From ce3f22740f86c68e5ac1572109772086d6c45534 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 09:28:39 +0200 Subject: [PATCH 1/9] IntegrationManagerVC: enableDebug is now defined in the kit --- .../Widgets/IntegrationManagerViewController.h | 2 +- .../Widgets/IntegrationManagerViewController.m | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.h b/Riot/ViewController/Widgets/IntegrationManagerViewController.h index 213147720..058567e6a 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.h +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.h @@ -25,7 +25,7 @@ FOUNDATION_EXPORT NSString *const kIntegrationManagerAddIntegrationScreen; `IntegrationManagerViewController` displays the Modular integration manager webapp into a webview. */ -@interface IntegrationManagerViewController : WebViewViewController +@interface IntegrationManagerViewController : WebViewViewController /** Initialise with params for the Modular interface webapp. diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 742c522e8..aabd191c8 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -19,8 +19,6 @@ #import "WidgetManager.h" #import "AppDelegate.h" -#import - NSString *const kIntegrationManagerMainScreen = nil; NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; @@ -147,18 +145,6 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', return url; } -- (void)enableDebug -{ - // Setup console.log() -> NSLog() route - JSContext *ctx = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; - ctx[@"console"][@"log"] = ^(JSValue * msg) { - NSLog(@"-- JavaScript: %@", msg); - }; - - // Redirect all console.* logging methods to console.log - [webView stringByEvaluatingJavaScriptFromString:@"console.debug = console.log; console.info = console.log; console.warn = console.log; console.error = console.log;"]; -} - - (void)showErrorAsAlert:(NSError*)error { NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; From d9d3d1deeb85db0a63da530c3febd9adbdfa77bb Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 10:12:02 +0200 Subject: [PATCH 2/9] Widget: Support new "m.widget" state event type but keep using "im.vector.modular.widgets" for operations while the move is not fully done (https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing) --- Riot/Utils/EventFormatter.m | 12 +++++++---- Riot/Utils/Widgets/Widget.m | 7 +++++-- Riot/Utils/Widgets/WidgetManager.h | 10 ++++++++-- Riot/Utils/Widgets/WidgetManager.m | 20 +++++++++++-------- .../IntegrationManagerViewController.m | 3 ++- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index a4a5c6f10..c22562a43 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -39,10 +39,10 @@ - (NSAttributedString *)attributedStringFromEvent:(MXEvent *)event withRoomState:(MXRoomState *)roomState error:(MXKEventFormatterError *)error { - // Build strings for modular widget events - // TODO: At the moment, we support only jitsi widgets + // Build strings for widget events if (event.eventType == MXEventTypeCustom - && [event.type isEqualToString:kWidgetEventTypeString]) + && ([event.type isEqualToString:kWidgetMatrixEventTypeString] + || [event.type isEqualToString:kWidgetModularEventTypeString])) { NSString *displayText; @@ -71,7 +71,11 @@ // This is a closed widget // Check if it corresponds to a jitsi widget by looking at other state events for // this jitsi widget (widget id = event.stateKey). - for (MXEvent *widgetStateEvent in [roomState stateEventsWithType:kWidgetEventTypeString]) + // Get all widgets state events in the room + NSMutableArray *widgetStateEvents = [NSMutableArray arrayWithArray:[roomState stateEventsWithType:kWidgetMatrixEventTypeString]]; + [widgetStateEvents addObjectsFromArray:[roomState stateEventsWithType:kWidgetModularEventTypeString]]; + + for (MXEvent *widgetStateEvent in widgetStateEvents) { if ([widgetStateEvent.stateKey isEqualToString:widget.widgetId]) { diff --git a/Riot/Utils/Widgets/Widget.m b/Riot/Utils/Widgets/Widget.m index 6ccddc56d..cbdd10608 100644 --- a/Riot/Utils/Widgets/Widget.m +++ b/Riot/Utils/Widgets/Widget.m @@ -22,9 +22,12 @@ - (instancetype)initWithWidgetEvent:(MXEvent *)widgetEvent inMatrixSession:(MXSession*)mxSession { - if (![widgetEvent.type isEqualToString:kWidgetEventTypeString]) + // TODO - Room widgets need to be moved to 'm.widget' state events + // https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing + if (![widgetEvent.type isEqualToString:kWidgetMatrixEventTypeString] + && ![widgetEvent.type isEqualToString:kWidgetModularEventTypeString]) { - // The Widget class works only with modular, aka "im.vector.modular.widgets", widgets + // The Widget class works only with modular, aka "m.widget" or "im.vector.modular.widgets", widgets return nil; } diff --git a/Riot/Utils/Widgets/WidgetManager.h b/Riot/Utils/Widgets/WidgetManager.h index 8c2e5d4c5..a10898e29 100644 --- a/Riot/Utils/Widgets/WidgetManager.h +++ b/Riot/Utils/Widgets/WidgetManager.h @@ -21,9 +21,15 @@ #import "Widget.h" /** - The type of matrix event used for modular widgets. + The type of matrix event used for matrix widgets. */ -FOUNDATION_EXPORT NSString *const kWidgetEventTypeString; +FOUNDATION_EXPORT NSString *const kWidgetMatrixEventTypeString; + +/** + The type of matrix event used for modular widgets. + TODO: It should be replaced by kWidgetMatrixEventTypeString. + */ +FOUNDATION_EXPORT NSString *const kWidgetModularEventTypeString; /** Known types widgets. diff --git a/Riot/Utils/Widgets/WidgetManager.m b/Riot/Utils/Widgets/WidgetManager.m index 3adf7db81..d795e77bc 100644 --- a/Riot/Utils/Widgets/WidgetManager.m +++ b/Riot/Utils/Widgets/WidgetManager.m @@ -20,7 +20,8 @@ #pragma mark - Contants -NSString *const kWidgetEventTypeString = @"im.vector.modular.widgets"; +NSString *const kWidgetMatrixEventTypeString = @"m.widget"; +NSString *const kWidgetModularEventTypeString = @"im.vector.modular.widgets"; NSString *const kWidgetTypeJitsi = @"jitsi"; NSString *const kWidgetManagerDidUpdateWidgetNotification = @"kWidgetManagerDidUpdateWidgetNotification"; @@ -102,10 +103,11 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; // Widget id -> widget NSMutableDictionary *widgets = [NSMutableDictionary dictionary]; - // Get all im.vector.modular.widgets state events in the room - NSMutableArray *widgetEvents = [NSMutableArray arrayWithArray:[room.state stateEventsWithType:kWidgetEventTypeString]]; + // Get all widgets state events in the room + NSMutableArray *widgetEvents = [NSMutableArray arrayWithArray:[room.state stateEventsWithType:kWidgetMatrixEventTypeString]]; + [widgetEvents addObjectsFromArray:[room.state stateEventsWithType:kWidgetModularEventTypeString]]; - // There can be several im.vector.modular.widgets state events for a same widget but + // 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 @@ -124,7 +126,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; return result; }]; - // Create each widget from its lastest im.vector.modular.widgets state event + // Create each widget from its lastest widgets state event for (MXEvent *widgetEvent in widgetEvents) { // Filter widget types if required @@ -192,7 +194,8 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; // Send a state event with the widget data // TODO: This API will be shortly replaced by a pure modular API - return [room sendStateEventOfType:kWidgetEventTypeString + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? + return [room sendStateEventOfType:kWidgetModularEventTypeString content:widgetContent stateKey:widgetId success:nil failure:failure]; @@ -247,7 +250,8 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; // Send a state event with an empty content to disable the widget // TODO: This API will be shortly replaced by a pure modular API - return [room sendStateEventOfType:kWidgetEventTypeString + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? + return [room sendStateEventOfType:kWidgetModularEventTypeString content:@{} stateKey:widgetId success:^(NSString *eventId) @@ -292,7 +296,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; NSString *hash = [NSString stringWithFormat:@"%p", mxSession]; - id listener = [mxSession listenToEventsOfTypes:@[kWidgetEventTypeString] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { + id listener = [mxSession listenToEventsOfTypes:@[kWidgetMatrixEventTypeString, kWidgetModularEventTypeString] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { typeof(self) self = weakSelf; diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index aabd191c8..35bd62e5e 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -522,7 +522,8 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', __weak __typeof__(self) weakSelf = self; - [room sendStateEventOfType:kWidgetEventTypeString + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? + [room sendStateEventOfType:kWidgetModularEventTypeString content:widgetEventContent stateKey:widget_id success:^(NSString *eventId) { From acf230465bb662d18e28baace4e251fb135dcb0e Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 10:46:33 +0200 Subject: [PATCH 3/9] Send Stickers: Pass widget id to widgets urls #1860 This is mandatory for the sticker picker widget --- Riot/Utils/Widgets/Widget.m | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Riot/Utils/Widgets/Widget.m b/Riot/Utils/Widgets/Widget.m index cbdd10608..0ac7714af 100644 --- a/Riot/Utils/Widgets/Widget.m +++ b/Riot/Utils/Widgets/Widget.m @@ -70,9 +70,6 @@ widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_display_name" withString:displayName]; widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_avatar_url" withString:avatarUrl]; - // And add the user scalar token - widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"&scalar_token=%@", scalarToken]]; - // Integrate widget data into widget url for (NSString *key in _data) { @@ -100,6 +97,14 @@ } } + // Add the user scalar token + widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"%@scalar_token=%@", + [widgetUrl containsString:@"?"] ? @"&" : @"?", + scalarToken]]; + + // Add the widget id + widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"&widgetId=%@", _widgetId]]; + success(widgetUrl); } From 6a043019266aee6b0916e01af6b0d2a3b14a24ad Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 11:15:18 +0200 Subject: [PATCH 4/9] Send Stickers: WidgetManager: add userWidgets and show them in the widget picker and within the list of installed widgets of the integration manager #1860 --- Riot/Utils/Widgets/WidgetManager.h | 8 ++++++++ Riot/Utils/Widgets/WidgetManager.m | 20 +++++++++++++++++++ Riot/ViewController/RoomViewController.m | 17 +++++++++++----- .../IntegrationManagerViewController.m | 14 ++++++++----- .../Widgets/WidgetPickerViewController.m | 11 ++++++++-- 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/Riot/Utils/Widgets/WidgetManager.h b/Riot/Utils/Widgets/WidgetManager.h index a10898e29..655a88aba 100644 --- a/Riot/Utils/Widgets/WidgetManager.h +++ b/Riot/Utils/Widgets/WidgetManager.h @@ -93,6 +93,14 @@ WidgetManagerErrorCode; */ - (NSArray *)widgetsNotOfTypes:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room; +/** + List all widgets of an account. + + @param mxSession the session of the user account. + @return a list of widgets. + */ +- (NSArray *)userWidgets:(MXSession*)mxSession; + /** Add a modular widget to a room. diff --git a/Riot/Utils/Widgets/WidgetManager.m b/Riot/Utils/Widgets/WidgetManager.m index d795e77bc..c004a36cf 100644 --- a/Riot/Utils/Widgets/WidgetManager.m +++ b/Riot/Utils/Widgets/WidgetManager.m @@ -172,6 +172,26 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; return activeWidgets; } +- (NSArray *)userWidgets:(MXSession*)mxSession +{ + // Get all widgets in the user account data + NSMutableArray *userWidgets = [NSMutableArray array]; + for (NSDictionary *widgetEventContent in [mxSession.accountData accountDataForEventType:@"m.widgets"].allValues) + { + MXEvent *widgetEvent = [MXEvent modelFromJSON:widgetEventContent]; + if (widgetEvent) + { + Widget *widget = [[Widget alloc] initWithWidgetEvent:widgetEvent inMatrixSession:mxSession]; + if (widget) + { + [userWidgets addObject:widget]; + } + } + } + + return userWidgets; +} + - (MXHTTPOperation *)createWidget:(NSString*)widgetId withContent:(NSDictionary*)widgetContent inRoom:(MXRoom*)room diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 8c1306325..71472f311 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -1257,9 +1257,10 @@ // If the setting is disabled, do not show the icon self.navigationItem.rightBarButtonItems = @[self.navigationItem.rightBarButtonItem]; } - else if (self.widgetsCount) + else if ([self widgetsCount:NO]) { // Show there are widgets by changing the "apps" icon color + // Show it in red only for room widgets, not user's widgets // TODO: Design must be reviewed UIImage *icon = self.navigationItem.rightBarButtonItems[1].image; icon = [MXKTools paintImage:icon withColor:kRiotColorPinkRed]; @@ -3063,7 +3064,7 @@ // Matrix Apps button else if (self.navigationItem.rightBarButtonItems.count == 2 && sender == self.navigationItem.rightBarButtonItems[1]) { - if (self.widgetsCount) + if ([self widgetsCount:YES]) { WidgetPickerViewController *widgetPicker = [[WidgetPickerViewController alloc] initForMXSession:self.roomDataSource.mxSession inRoom:self.roomDataSource.roomId]; @@ -3643,10 +3644,16 @@ [[AppDelegate theDelegate] showErrorAsAlert:error]; } -- (NSUInteger)widgetsCount +- (NSUInteger)widgetsCount:(BOOL)includeUserWidgets { - return [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] - inRoom:self.roomDataSource.room].count; + NSUInteger widgetsCount = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] + inRoom:self.roomDataSource.room].count; + if (includeUserWidgets) + { + widgetsCount = [[WidgetManager sharedManager] userWidgets:self.roomDataSource.room.mxSession].count; + } + + return widgetsCount; } #pragma mark - Unreachable Network Handling diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 35bd62e5e..e37dea2b9 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -551,20 +551,24 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', - (void)getWidgets:(NSDictionary*)eventData { MXRoom *room = [self roomCheckWithEvent:eventData]; + NSMutableArray *widgetStateEvents = [NSMutableArray array]; if (room) { NSArray *widgets = [[WidgetManager sharedManager] widgetsInRoom:room]; - - NSMutableArray *widgetStateEvents = [NSMutableArray arrayWithCapacity:widgets.count]; - for (Widget *widget in widgets) { [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary]; } - - [self sendNSObjectResponse:widgetStateEvents toEvent:eventData]; } + + // Add user widgets (not linked to a specific room) + for (Widget *widget in [[WidgetManager sharedManager] userWidgets:mxSession]) + { + [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary]; + } + + [self sendNSObjectResponse:widgetStateEvents toEvent:eventData]; } - (void)canSendEvent:(NSDictionary*)eventData diff --git a/Riot/ViewController/Widgets/WidgetPickerViewController.m b/Riot/ViewController/Widgets/WidgetPickerViewController.m index 59bf7c339..958f263ff 100644 --- a/Riot/ViewController/Widgets/WidgetPickerViewController.m +++ b/Riot/ViewController/Widgets/WidgetPickerViewController.m @@ -53,12 +53,19 @@ MXRoom *room = [mxSession roomWithRoomId:roomId]; - NSArray *widgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] + NSArray *roomWidgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] inRoom:room]; + + NSArray *userWidgets = [[WidgetManager sharedManager] userWidgets:room.mxSession]; + + NSMutableArray *widgets = [NSMutableArray array]; + [widgets addObjectsFromArray:roomWidgets]; + [widgets addObjectsFromArray:userWidgets]; + // List widgets for (Widget *widget in widgets) { - alertAction = [UIAlertAction actionWithTitle:widget.name + alertAction = [UIAlertAction actionWithTitle:widget.name ? widget.name : widget.type style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { From b1d7617d05194cf881f7560e900a534e92b51b28 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 14:18:24 +0200 Subject: [PATCH 5/9] Widgets: Make a generic postMessage API in WidgetVC It will be used for https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit#. The Modular postMessage API becomes a specialisation of it. --- Riot.xcodeproj/project.pbxproj | 14 +- ...ntegrationManager.js => postMessageAPI.js} | 4 +- .../IntegrationManagerViewController.h | 8 +- .../IntegrationManagerViewController.m | 199 +----------------- .../Widgets/WidgetViewController.h | 63 ++++++ .../Widgets/WidgetViewController.m | 197 ++++++++++++++++- 6 files changed, 276 insertions(+), 209 deletions(-) rename Riot/Assets/js/{IntegrationManager.js => postMessageAPI.js} (96%) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 38ba5a727..2908c6779 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -30,7 +30,7 @@ 24EEE5A11F23A09A00B3C705 /* RiotDesignValues.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BC171E7009EC00A9B29C /* RiotDesignValues.m */; }; 24EEE5A21F23A8B400B3C705 /* MXRoom+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BBE81E7009EC00A9B29C /* MXRoom+Riot.m */; }; 24EEE5A31F23A8C300B3C705 /* AvatarGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BC111E7009EC00A9B29C /* AvatarGenerator.m */; }; - 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; + 24EEE5A41F24C06E00B3C705 /* (null) in Resources */ = {isa = PBXBuildFile; }; 24EEE5A81F25529600B3C705 /* cancel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A121EDEE65000F5DC9A /* cancel@3x.png */; }; 24EEE5A91F25529900B3C705 /* cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A111EDEE65000F5DC9A /* cancel@2x.png */; }; 24EEE5AA1F25529C00B3C705 /* cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A101EDEE65000F5DC9A /* cancel.png */; }; @@ -70,11 +70,11 @@ 327382C41F276AED00356143 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382C01F276AED00356143 /* Vector.strings */; }; 32795C0B203DA4C4002420E2 /* DisabledRoomInputToolbarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32795C09203DA4C3002420E2 /* DisabledRoomInputToolbarView.m */; }; 32795C0C203DA4C4002420E2 /* DisabledRoomInputToolbarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32795C0A203DA4C4002420E2 /* DisabledRoomInputToolbarView.xib */; }; + 3284A35120A07C210044F922 /* postMessageAPI.js in Resources */ = {isa = PBXBuildFile; fileRef = 3284A35020A07C210044F922 /* postMessageAPI.js */; }; 3284D7FF1FBB34B70090AA80 /* RoomKeyRequestViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3284D7FE1FBB34B70090AA80 /* RoomKeyRequestViewController.m */; }; 32918EA91F473BDB0076CA16 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32918EA51F473BDB0076CA16 /* Localizable.strings */; }; 32918EAA1F473BDB0076CA16 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32918EA71F473BDB0076CA16 /* Vector.strings */; }; 32935CB11F6056FD006888C8 /* IntegrationManagerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32935CB01F6056FD006888C8 /* IntegrationManagerViewController.m */; }; - 32935CB41F628BCE006888C8 /* IntegrationManager.js in Resources */ = {isa = PBXBuildFile; fileRef = 32935CB31F628BCE006888C8 /* IntegrationManager.js */; }; 329DCE191F988F8100468420 /* riot_icon_callkit.png in Resources */ = {isa = PBXBuildFile; fileRef = 329DCE161F988F8100468420 /* riot_icon_callkit.png */; }; 329DCE1A1F988F8100468420 /* riot_icon_callkit@2.png in Resources */ = {isa = PBXBuildFile; fileRef = 329DCE171F988F8100468420 /* riot_icon_callkit@2.png */; }; 329DCE1B1F988F8100468420 /* riot_icon_callkit@3.png in Resources */ = {isa = PBXBuildFile; fileRef = 329DCE181F988F8100468420 /* riot_icon_callkit@3.png */; }; @@ -733,13 +733,13 @@ 32795C08203DA4C3002420E2 /* DisabledRoomInputToolbarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DisabledRoomInputToolbarView.h; sourceTree = ""; }; 32795C09203DA4C3002420E2 /* DisabledRoomInputToolbarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DisabledRoomInputToolbarView.m; sourceTree = ""; }; 32795C0A203DA4C4002420E2 /* DisabledRoomInputToolbarView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DisabledRoomInputToolbarView.xib; sourceTree = ""; }; + 3284A35020A07C210044F922 /* postMessageAPI.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = postMessageAPI.js; sourceTree = ""; }; 3284D7FD1FBB34B70090AA80 /* RoomKeyRequestViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoomKeyRequestViewController.h; sourceTree = ""; }; 3284D7FE1FBB34B70090AA80 /* RoomKeyRequestViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoomKeyRequestViewController.m; sourceTree = ""; }; 32918EA61F473BDB0076CA16 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Localizable.strings; sourceTree = ""; }; 32918EA81F473BDB0076CA16 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Vector.strings; sourceTree = ""; }; 32935CAF1F6056FD006888C8 /* IntegrationManagerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegrationManagerViewController.h; sourceTree = ""; }; 32935CB01F6056FD006888C8 /* IntegrationManagerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegrationManagerViewController.m; sourceTree = ""; }; - 32935CB31F628BCE006888C8 /* IntegrationManager.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = IntegrationManager.js; sourceTree = ""; }; 329DCE161F988F8100468420 /* riot_icon_callkit.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = riot_icon_callkit.png; sourceTree = ""; }; 329DCE171F988F8100468420 /* riot_icon_callkit@2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "riot_icon_callkit@2.png"; sourceTree = ""; }; 329DCE181F988F8100468420 /* riot_icon_callkit@3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "riot_icon_callkit@3.png"; sourceTree = ""; }; @@ -1627,7 +1627,7 @@ 32935CB21F628B98006888C8 /* js */ = { isa = PBXGroup; children = ( - 32935CB31F628BCE006888C8 /* IntegrationManager.js */, + 3284A35020A07C210044F922 /* postMessageAPI.js */, ); path = js; sourceTree = ""; @@ -2911,9 +2911,9 @@ F0BFBDBB1FF3BF6F00C88726 /* Vector.strings in Resources */, F0BFBDBF1FF3BFD200C88726 /* Vector.strings in Resources */, 24D6B35E1F3CA03E00FC7A71 /* FallbackViewController.xib in Resources */, - 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */, + 24EEE5A41F24C06E00B3C705 /* (null) in Resources */, 2439DD641F6BBEA50090F42D /* RecentRoomTableViewCell.xib in Resources */, - 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */, + 24EEE5A41F24C06E00B3C705 /* (null) in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3270,7 +3270,6 @@ F083BD961E7009ED00A9B29C /* leave@2x.png in Resources */, F0E05A461EA0F9EB004B83FB /* tab_rooms@2x.png in Resources */, 32AE61F41F0D2183007255F4 /* Vector.strings in Resources */, - 32935CB41F628BCE006888C8 /* IntegrationManager.js in Resources */, F083BD851E7009ED00A9B29C /* favouriteOff@3x.png in Resources */, F083BD541E7009ED00A9B29C /* camera_switch@2x.png in Resources */, F07CB3992029B20100C29C20 /* GroupTableViewCellWithSwitch.xib in Resources */, @@ -3292,6 +3291,7 @@ 327382C41F276AED00356143 /* Vector.strings in Resources */, 32BB89EF204D86DA002F3AEC /* Localizable.strings in Resources */, F083BDE51E7009ED00A9B29C /* voice_call_icon@3x.png in Resources */, + 3284A35120A07C210044F922 /* postMessageAPI.js in Resources */, 322806A11F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.xib in Resources */, F083BE201E7009ED00A9B29C /* RoomParticipantsViewController.xib in Resources */, F083BE471E7009ED00A9B29C /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.xib in Resources */, diff --git a/Riot/Assets/js/IntegrationManager.js b/Riot/Assets/js/postMessageAPI.js similarity index 96% rename from Riot/Assets/js/IntegrationManager.js rename to Riot/Assets/js/postMessageAPI.js index 5dc414677..bc30830e3 100644 --- a/Riot/Assets/js/IntegrationManager.js +++ b/Riot/Assets/js/postMessageAPI.js @@ -28,7 +28,7 @@ window.riotIOS.sendObjectMessageToObjC = function(parameters) { window.riotIOS.events = {}; -// Listen to messages posted by Modular +// Listen to messages posted by the widget window.riotIOS.onMessage = function(event) { // Do not SPAM ObjC with event already managed @@ -50,7 +50,7 @@ window.riotIOS.onMessage = function(event) { window.addEventListener('message', riotIOS.onMessage, false); -// ObjC -> Modular JS bridge +// ObjC -> Widget JS bridge window.riotIOS.sendResponse = function(eventId, res) { // Retrieve the correspong JS event diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.h b/Riot/ViewController/Widgets/IntegrationManagerViewController.h index 058567e6a..3087c57f1 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.h +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.h @@ -14,9 +14,7 @@ limitations under the License. */ -#import "WebViewViewController.h" - -#import +#import "WidgetViewController.h" FOUNDATION_EXPORT NSString *const kIntegrationManagerMainScreen; FOUNDATION_EXPORT NSString *const kIntegrationManagerAddIntegrationScreen; @@ -24,8 +22,10 @@ FOUNDATION_EXPORT NSString *const kIntegrationManagerAddIntegrationScreen; /** `IntegrationManagerViewController` displays the Modular integration manager webapp into a webview. + + It reuses the postMessage API pipe defined in `WidgetViewController`. */ -@interface IntegrationManagerViewController : WebViewViewController +@interface IntegrationManagerViewController : WidgetViewController /** Initialise with params for the Modular interface webapp. diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index e37dea2b9..af4a70d5b 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -17,13 +17,10 @@ #import "IntegrationManagerViewController.h" #import "WidgetManager.h" -#import "AppDelegate.h" NSString *const kIntegrationManagerMainScreen = nil; NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; -NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; - @interface IntegrationManagerViewController () { @@ -61,19 +58,6 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', operation = nil; } -- (void)viewDidLoad -{ - [super viewDidLoad]; - - webView.scalesPageToFit = NO; - webView.scrollView.bounces = NO; - - // Disable opacity so that the webview background uses the current interface theme - webView.opaque = NO; - - webView.delegate = self; -} - - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; @@ -84,7 +68,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', [self startActivityIndicator]; - // Make sure we a scalar token + // Make sure we have a scalar token operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession success:^(NSString *theScalarToken) { typeof(self) self = weakSelf; @@ -145,123 +129,11 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', return url; } -- (void)showErrorAsAlert:(NSError*)error -{ - NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; - NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; - if (!title) - { - if (msg) - { - title = msg; - msg = nil; - } - else - { - title = [NSBundle mxk_localizedStringForKey:@"error"]; - } - } - - __weak __typeof__(self) weakSelf = self; - - UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - typeof(self) self = weakSelf; - - if (self) - { - // Leave this Intergrations Manager VC - [self withdrawViewControllerAnimated:YES completion:nil]; - } - - }]]; - - [self presentViewController:alert animated:YES completion:nil]; -} - -#pragma mark - UIWebViewDelegate - --(void)webViewDidFinishLoad:(UIWebView *)theWebView -{ - [self enableDebug]; - - // Setup js code - NSString *path = [[NSBundle mainBundle] pathForResource:@"IntegrationManager" ofType:@"js"]; - NSString *js = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; - [webView stringByEvaluatingJavaScriptFromString:js]; - - [self stopActivityIndicator]; - - // Check connectivity - if ([AppDelegate theDelegate].isOffline) - { - // The web page may be in the cache, so its loading will be successful - // but we cannot go further, it often leads to a blank screen. - // So, display an error so that the user can escape. - NSError *error = [NSError errorWithDomain:NSURLErrorDomain - code:NSURLErrorNotConnectedToInternet - userInfo:@{ - NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"network_offline_prompt", @"Vector", nil) - }]; - [self showErrorAsAlert:error]; - } -} - -- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType -{ - NSString *urlString = [[request URL] absoluteString]; - - if ([urlString hasPrefix:@"js:"]) - { - // Listen only to scheme of the JS-UIWebView bridge - NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; - NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; - - NSError *error; - NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers - error:&error]; - if (!error) - { - [self onMessage:parameters]; - } - - return NO; - } - - if (navigationType == UIWebViewNavigationTypeLinkClicked ) - { - // Open links outside the app - [[UIApplication sharedApplication] openURL:[request URL]]; - return NO; - } - - return YES; -} - -- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error -{ - // Filter out the users's scalar token - NSString *errorDescription = error.description; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"scalar_token=\\w*" - options:NSRegularExpressionCaseInsensitive error:nil]; - errorDescription = [regex stringByReplacingMatchesInString:errorDescription - options:0 - range:NSMakeRange(0, errorDescription.length) - withTemplate:@"scalar_token=..."]; - - NSLog(@"[IntegrationManagerVC] didFailLoadWithError: %@", errorDescription); - - [self stopActivityIndicator]; - [self showErrorAsAlert:error]; -} - -#pragma mark - Modular postMessage API +#pragma mark - Modular postMessage API implementation - (void)onMessage:(NSDictionary*)JSData { + // Implement the Modular postMessage API NSDictionary *eventData; MXJSONModelSetDictionary(eventData, JSData[@"event.data"]); @@ -355,70 +227,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', } } -- (void)sendBoolResponse:(BOOL)response toEvent:(NSDictionary*)eventData -{ - // Convert BOOL to "true" or "false" - NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, - eventData[@"_id"], - response ? @"true" : @"false"]; - - [webView stringByEvaluatingJavaScriptFromString:js]; -} - -- (void)sendIntegerResponse:(NSUInteger)response toEvent:(NSDictionary*)eventData -{ - NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, - eventData[@"_id"], - @(response)]; - - [webView stringByEvaluatingJavaScriptFromString:js]; -} - -- (void)sendNSObjectResponse:(NSObject*)response toEvent:(NSDictionary*)eventData -{ - NSString *jsString; - - if (response) - { - // Convert response into a JS object through a JSON string - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response - options:0 - error:0]; - NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - - jsString = [NSString stringWithFormat:@"JSON.parse('%@')", jsonString]; - } - else - { - jsString = @"null"; - } - - NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, - eventData[@"_id"], - jsString]; - - [webView stringByEvaluatingJavaScriptFromString:js]; -} - -- (void)sendError:(NSString*)message toEvent:(NSDictionary*)eventData -{ - NSLog(@"[IntegrationManagerVC] sendError: Action %@ failed with message: %@", eventData[@"action"], message); - - // TODO: JS has an additional optional parameter: nestedError - [self sendNSObjectResponse:@{ - @"error": @{ - @"message": message - } - } - toEvent:eventData]; -} - -- (void)sendLocalisedError:(NSString*)errorKey toEvent:(NSDictionary*)eventData -{ - [self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toEvent:eventData]; -} - -#pragma mark - Modular postMessage Implementation +#pragma mark - Private methods - (MXRoom *)roomCheckWithEvent:(NSDictionary*)eventData { diff --git a/Riot/ViewController/Widgets/WidgetViewController.h b/Riot/ViewController/Widgets/WidgetViewController.h index e4bdf9b75..0ae05af18 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.h +++ b/Riot/ViewController/Widgets/WidgetViewController.h @@ -20,6 +20,9 @@ /** `WidgetViewController` displays widget within a webview. + + It also exposes a generic pipe, the postMessage API, to communicate with the + content within the webview, ie the widget (matrix app). */ @interface WidgetViewController : WebViewViewController @@ -31,4 +34,64 @@ */ - (instancetype)initWithUrl:(NSString*)widgetUrl forWidget:(Widget*)widget; +/** + Display an alert over this controller. + + @param error the error to display. + */ +- (void)showErrorAsAlert:(NSError*)error; + + +#pragma mark - postMessage API + +/** + Callback called when the widget make a postMessage API request. + + This method can be overidden to implement a specific API between the matrix client + and widget. + + @param @TODO + */ +- (void)onMessage:(NSDictionary*)JSData; + +/** + Send a boolean response to a request from the widget. + + @param response the response to send. + @param @TODO + */ +- (void)sendBoolResponse:(BOOL)response toEvent:(NSDictionary*)eventData; + +/** + Send an integer response to a request from the widget. + + @param response the response to send. + @param @TODO + */ +- (void)sendIntegerResponse:(NSUInteger)response toEvent:(NSDictionary*)eventData; + +/** + Send a serialiable object response to a request the widget. + + @param response the response to send. + @param @TODO + */ +- (void)sendNSObjectResponse:(NSObject*)response toEvent:(NSDictionary*)eventData; + +/** + Send a message error to a request from the widget. + + @param message the error message. + @param @TODO + */ +- (void)sendError:(NSString*)message toEvent:(NSDictionary*)eventData; + +/** + Send a localised message error to a request from the widget. + + @param errorKey the string id of the message error. + @param @TODO + */ +- (void)sendLocalisedError:(NSString*)errorKey toEvent:(NSDictionary*)eventData; + @end diff --git a/Riot/ViewController/Widgets/WidgetViewController.m b/Riot/ViewController/Widgets/WidgetViewController.m index c031f823b..ddc2d04aa 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.m +++ b/Riot/ViewController/Widgets/WidgetViewController.m @@ -16,6 +16,10 @@ #import "WidgetViewController.h" +#import "AppDelegate.h" + +NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; + @interface WidgetViewController () { Widget *widget; @@ -42,7 +46,198 @@ webView.scalesPageToFit = NO; webView.scrollView.bounces = NO; - self.navigationItem.title = widget.name; + // Disable opacity so that the webview background uses the current interface theme + webView.opaque = NO; + + if (widget) + { + self.navigationItem.title = widget.name ? widget.name : widget.type; + } +} + +- (void)showErrorAsAlert:(NSError*)error +{ + NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; + NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; + if (!title) + { + if (msg) + { + title = msg; + msg = nil; + } + else + { + title = [NSBundle mxk_localizedStringForKey:@"error"]; + } + } + + __weak __typeof__(self) weakSelf = self; + + UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + typeof(self) self = weakSelf; + + if (self) + { + // Leave this widget VC + [self withdrawViewControllerAnimated:YES completion:nil]; + } + + }]]; + + [self presentViewController:alert animated:YES completion:nil]; +} + +#pragma mark - UIWebViewDelegate + +-(void)webViewDidFinishLoad:(UIWebView *)theWebView +{ + [self enableDebug]; + + // Setup js code + NSString *path = [[NSBundle mainBundle] pathForResource:@"postMessageAPI" ofType:@"js"]; + NSString *js = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [webView stringByEvaluatingJavaScriptFromString:js]; + + [self stopActivityIndicator]; + + // Check connectivity + if ([AppDelegate theDelegate].isOffline) + { + // The web page may be in the cache, so its loading will be successful + // but we cannot go further, it often leads to a blank screen. + // So, display an error so that the user can escape. + NSError *error = [NSError errorWithDomain:NSURLErrorDomain + code:NSURLErrorNotConnectedToInternet + userInfo:@{ + NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"network_offline_prompt", @"Vector", nil) + }]; + [self showErrorAsAlert:error]; + } +} + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType +{ + NSString *urlString = [[request URL] absoluteString]; + + if ([urlString hasPrefix:@"js:"]) + { + // Listen only to scheme of the JS-UIWebView bridge + NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; + NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + + NSError *error; + NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers + error:&error]; + if (!error) + { + [self onMessage:parameters]; + } + + return NO; + } + + if (navigationType == UIWebViewNavigationTypeLinkClicked ) + { + // Open links outside the app + [[UIApplication sharedApplication] openURL:[request URL]]; + return NO; + } + + return YES; +} + +- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error +{ + // Filter out the users's scalar token + NSString *errorDescription = error.description; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"scalar_token=\\w*" + options:NSRegularExpressionCaseInsensitive error:nil]; + errorDescription = [regex stringByReplacingMatchesInString:errorDescription + options:0 + range:NSMakeRange(0, errorDescription.length) + withTemplate:@"scalar_token=..."]; + + NSLog(@"[WidgetVC] didFailLoadWithError: %@", errorDescription); + + [self stopActivityIndicator]; + [self showErrorAsAlert:error]; +} + +#pragma mark - postMessage API + +- (void)onMessage:(NSDictionary*)JSData +{ + // @TODO + NSDictionary *eventData; + MXJSONModelSetDictionary(eventData, JSData[@"event.data"]); +} + +- (void)sendBoolResponse:(BOOL)response toEvent:(NSDictionary*)eventData +{ + // Convert BOOL to "true" or "false" + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, + eventData[@"_id"], + response ? @"true" : @"false"]; + + [webView stringByEvaluatingJavaScriptFromString:js]; +} + +- (void)sendIntegerResponse:(NSUInteger)response toEvent:(NSDictionary*)eventData +{ + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, + eventData[@"_id"], + @(response)]; + + [webView stringByEvaluatingJavaScriptFromString:js]; +} + +- (void)sendNSObjectResponse:(NSObject*)response toEvent:(NSDictionary*)eventData +{ + NSString *jsString; + + if (response) + { + // Convert response into a JS object through a JSON string + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response + options:0 + error:0]; + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + + jsString = [NSString stringWithFormat:@"JSON.parse('%@')", jsonString]; + } + else + { + jsString = @"null"; + } + + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, + eventData[@"_id"], + jsString]; + + [webView stringByEvaluatingJavaScriptFromString:js]; +} + +- (void)sendError:(NSString*)message toEvent:(NSDictionary*)eventData +{ + NSLog(@"[WidgetVC] sendError: Action %@ failed with message: %@", eventData[@"action"], message); + + // TODO: JS has an additional optional parameter: nestedError + [self sendNSObjectResponse:@{ + @"error": @{ + @"message": message + } + } + toEvent:eventData]; +} + +- (void)sendLocalisedError:(NSString*)errorKey toEvent:(NSDictionary*)eventData +{ + [self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toEvent:eventData]; } @end From 1834a901080807683d859763515dd718a76837c8 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 15:33:37 +0200 Subject: [PATCH 6/9] Widget postMessage API: Refactor to avoid to play with internal event.data._id field --- .../IntegrationManagerViewController.m | 158 +++++++++--------- .../Widgets/WidgetViewController.h | 25 +-- .../Widgets/WidgetViewController.m | 44 +++-- 3 files changed, 118 insertions(+), 109 deletions(-) diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index af4a70d5b..86e118ecb 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -131,17 +131,13 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; #pragma mark - Modular postMessage API implementation -- (void)onMessage:(NSDictionary*)JSData +- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData { - // Implement the Modular postMessage API - NSDictionary *eventData; - MXJSONModelSetDictionary(eventData, JSData[@"event.data"]); - NSString *roomIdInEvent, *userId, *action; - MXJSONModelSetString(roomIdInEvent, eventData[@"room_id"]); - MXJSONModelSetString(userId, eventData[@"user_id"]); - MXJSONModelSetString(action, eventData[@"action"]); + MXJSONModelSetString(roomIdInEvent, requestData[@"room_id"]); + MXJSONModelSetString(userId, requestData[@"user_id"]); + MXJSONModelSetString(action, requestData[@"action"]); if ([action isEqualToString:@"close_scalar"]) { @@ -151,13 +147,13 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; if (!roomIdInEvent) { - [self sendLocalisedError:@"widget_integration_missing_room_id" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_missing_room_id" toRequest:requestId]; return; } if (![roomIdInEvent isEqualToString:roomId]) { - [self sendError:[NSString stringWithFormat:NSLocalizedStringFromTable(@"widget_integration_room_not_visible", @"Vector", nil), roomIdInEvent] toEvent:eventData]; + [self sendError:[NSString stringWithFormat:NSLocalizedStringFromTable(@"widget_integration_room_not_visible", @"Vector", nil), roomIdInEvent] toRequest:requestId]; return; } @@ -165,86 +161,86 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; // These APIs don't require userId if ([@"join_rules_state" isEqualToString:action]) { - [self getJoinRules:eventData]; + [self getJoinRules:requestId data:requestData]; return; } else if ([@"set_plumbing_state" isEqualToString:action]) { - [self setPlumbingState:eventData]; + [self setPlumbingState:requestId data:requestData]; return; } else if ([@"get_membership_count" isEqualToString:action]) { - [self getMembershipCount:eventData]; + [self getMembershipCount:requestId data:requestData]; return; } else if ([@"set_widget" isEqualToString:action]) { - [self setWidget:eventData]; + [self setWidget:requestId data:requestData]; return; } else if ([@"get_widgets" isEqualToString:action]) { - [self getWidgets:eventData]; + [self getWidgets:requestId data:requestData]; return; } else if ([@"can_send_event" isEqualToString:action]) { - [self canSendEvent:eventData]; + [self canSendEvent:requestId data:requestData]; return; } if (!userId) { - [self sendLocalisedError:@"widget_integration_missing_user_id" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_missing_user_id" toRequest:requestId]; return; } if ([@"membership_state" isEqualToString:action]) { - [self getMembershipState:userId eventData:eventData]; + [self getMembershipState:userId request:requestId data:requestData]; } else if ([@"invite" isEqualToString:action]) { - [self inviteUser:userId eventData:eventData]; + [self inviteUser:userId request:requestId data:requestData]; } else if ([@"bot_options" isEqualToString:action]) { - [self getBotOptions:userId eventData:eventData]; + [self getBotOptions:userId request:requestId data:requestData]; } else if ([@"set_bot_options" isEqualToString:action]) { - [self setBotOptions:userId eventData:eventData]; + [self setBotOptions:userId request:requestId data:requestData]; } else if ([@"set_bot_power" isEqualToString:action]) { - [self setBotPower:userId eventData:eventData]; + [self setBotPower:userId request:requestId data:requestData]; } else { - NSLog(@"[IntegrationManagerViewControllerVC] Unhandled postMessage event with action %@: %@", action, JSData); + NSLog(@"[IntegrationManagerViewControllerVC] Unhandled postMessage event with action %@: %@", action, requestData); } } #pragma mark - Private methods -- (MXRoom *)roomCheckWithEvent:(NSDictionary*)eventData +- (MXRoom *)roomCheckForRequest:(NSString*)requestId data:(NSDictionary*)requestData { MXRoom *room = [mxSession roomWithRoomId:roomId]; if (!room) { - [self sendLocalisedError:@"widget_integration_room_not_recognised" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_room_not_recognised" toRequest:requestId]; } return room; } -- (void)inviteUser:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)inviteUser:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to invite %@ into room %@.", userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { @@ -254,7 +250,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } else { @@ -268,7 +264,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -276,18 +272,18 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_need_to_be_able_to_invite" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_need_to_be_able_to_invite" toRequest:requestId]; } }]; } } } -- (void)setWidget:(NSDictionary*)eventData +- (void)setWidget:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to set widget in room %@.", roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { @@ -295,15 +291,15 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; NSString *widgetName; // optional NSDictionary *widgetData ; // optional - MXJSONModelSetString(widget_id, eventData[@"widget_id"]); - MXJSONModelSetString(widgetType, eventData[@"type"]); - MXJSONModelSetString(widgetUrl, eventData[@"url"]); - MXJSONModelSetString(widgetName, eventData[@"name"]); - MXJSONModelSetDictionary(widgetData, eventData[@"data"]); + MXJSONModelSetString(widget_id, requestData[@"widget_id"]); + MXJSONModelSetString(widgetType, requestData[@"type"]); + MXJSONModelSetString(widgetUrl, requestData[@"url"]); + MXJSONModelSetString(widgetName, requestData[@"name"]); + MXJSONModelSetDictionary(widgetData, requestData[@"data"]); if (!widget_id) { - [self sendLocalisedError:@"widget_integration_unable_to_create" toEvent:eventData]; // new Error("Missing required widget fields.")); + [self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; // new Error("Missing required widget fields.")); return; } @@ -312,7 +308,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; { if (!widgetType) { - [self sendLocalisedError:@"widget_integration_unable_to_create" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; return; } @@ -343,7 +339,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -351,15 +347,15 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; } }]; } } -- (void)getWidgets:(NSDictionary*)eventData +- (void)getWidgets:(NSString*)requestId data:(NSDictionary*)requestData { - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; NSMutableArray *widgetStateEvents = [NSMutableArray array]; if (room) @@ -377,26 +373,26 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary]; } - [self sendNSObjectResponse:widgetStateEvents toEvent:eventData]; + [self sendNSObjectResponse:widgetStateEvents toRequest:requestId]; } -- (void)canSendEvent:(NSDictionary*)eventData +- (void)canSendEvent:(NSString*)requestId data:(NSDictionary*)requestData { NSString *eventType; BOOL isState = NO; - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { if (room.state.membership != MXMembershipJoin) { - [self sendLocalisedError:@"widget_integration_must_be_in_room" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_must_be_in_room" toRequest:requestId]; return; } - MXJSONModelSetString(eventType, eventData[@"event_type"]); - MXJSONModelSetBoolean(isState, eventData[@"is_state"]); + MXJSONModelSetString(eventType, requestData[@"event_type"]); + MXJSONModelSetBoolean(isState, requestData[@"is_state"]); MXRoomPowerLevels *powerLevels = room.state.powerLevels; NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:mxSession.myUser.userId]; @@ -414,48 +410,48 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; if (canSend) { - [self sendBoolResponse:YES toEvent:eventData]; + [self sendBoolResponse:YES toRequest:requestId]; } else { - [self sendLocalisedError:@"widget_integration_no_permission_in_room" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_no_permission_in_room" toRequest:requestId]; } } } -- (void)getMembershipState:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)getMembershipState:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] membership_state of %@ in room %@ requested.", userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { MXRoomMember *member = [room.state memberWithUserId:userId]; - [self sendNSObjectResponse:member.originalEvent.content toEvent:eventData]; + [self sendNSObjectResponse:member.originalEvent.content toRequest:requestId]; } } -- (void)getJoinRules:(NSDictionary*)eventData +- (void)getJoinRules:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] join_rules of %@ requested.", roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { MXEvent *event = [room.state stateEventsWithType:kMXEventTypeStringRoomJoinRules].lastObject; - [self sendNSObjectResponse:event.JSONDictionary toEvent:eventData]; + [self sendNSObjectResponse:event.JSONDictionary toRequest:requestId]; } } -- (void)setPlumbingState:(NSDictionary*)eventData +- (void)setPlumbingState:(NSString*)requestId data:(NSDictionary*)requestData { - NSLog(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", eventData[@"status"], roomId); + NSLog(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", requestData[@"status"], roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSString *status; - MXJSONModelSetString(status, eventData[@"status"]); + MXJSONModelSetString(status, requestData[@"status"]); if (status) { @@ -474,7 +470,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -482,7 +478,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; } }]; } @@ -493,11 +489,11 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; } } -- (void)getBotOptions:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)getBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to get options for bot %@ in room %@", userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSString *stateKey = [NSString stringWithFormat:@"_%@", userId]; @@ -517,19 +513,19 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; } } - [self sendNSObjectResponse:botOptionsEvent.JSONDictionary toEvent:eventData]; + [self sendNSObjectResponse:botOptionsEvent.JSONDictionary toRequest:requestId]; } } -- (void)setBotOptions:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)setBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to set options for bot %@ in room %@", userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSDictionary *content; - MXJSONModelSetDictionary(content, eventData[@"content"]); + MXJSONModelSetDictionary(content, requestData[@"content"]); if (content) { @@ -548,7 +544,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -556,7 +552,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; } }]; } @@ -567,15 +563,15 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; } } -- (void)setBotPower:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)setBotPower:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { - NSLog(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", eventData[@"level"], userId, roomId); + NSLog(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", requestData[@"level"], userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSInteger level = -1; - MXJSONModelSetInteger(level, eventData[@"level"]); + MXJSONModelSetInteger(level, requestData[@"level"]); if (level >= 0) { @@ -589,7 +585,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -597,25 +593,25 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; } }]; } else { NSLog(@"[IntegrationManagerVC] setBotPower. Power level must be positive integer."); - [self sendLocalisedError:@"widget_integration_positive_power_level" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_positive_power_level" toRequest:requestId]; } } } -- (void)getMembershipCount:(NSDictionary*)eventData +- (void)getMembershipCount:(NSString*)requestId data:(NSDictionary*)requestData { - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSUInteger membershipCount = room.state.joinedMembers.count; - [self sendIntegerResponse:membershipCount toEvent:eventData]; + [self sendIntegerResponse:membershipCount toRequest:requestId]; } } diff --git a/Riot/ViewController/Widgets/WidgetViewController.h b/Riot/ViewController/Widgets/WidgetViewController.h index 0ae05af18..42b0c67dd 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.h +++ b/Riot/ViewController/Widgets/WidgetViewController.h @@ -50,48 +50,49 @@ This method can be overidden to implement a specific API between the matrix client and widget. - @param @TODO + @param requestId the id of the widget request. + @param requestData the request data. */ -- (void)onMessage:(NSDictionary*)JSData; +- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData; /** Send a boolean response to a request from the widget. @param response the response to send. - @param @TODO + @param requestId the id of the widget request. */ -- (void)sendBoolResponse:(BOOL)response toEvent:(NSDictionary*)eventData; +- (void)sendBoolResponse:(BOOL)response toRequest:(NSString*)requestId; /** Send an integer response to a request from the widget. @param response the response to send. - @param @TODO + @param requestId the id of the widget request. */ -- (void)sendIntegerResponse:(NSUInteger)response toEvent:(NSDictionary*)eventData; +- (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId; /** Send a serialiable object response to a request the widget. @param response the response to send. - @param @TODO + @param requestId the id of the widget request. */ -- (void)sendNSObjectResponse:(NSObject*)response toEvent:(NSDictionary*)eventData; +- (void)sendNSObjectResponse:(NSObject*)response toRequest:(NSString*)requestId; /** Send a message error to a request from the widget. @param message the error message. - @param @TODO + @param requestId the id of the widget request. */ -- (void)sendError:(NSString*)message toEvent:(NSDictionary*)eventData; +- (void)sendError:(NSString*)message toRequest:(NSString*)requestId; /** Send a localised message error to a request from the widget. @param errorKey the string id of the message error. - @param @TODO + @param requestId the id of the widget request. */ -- (void)sendLocalisedError:(NSString*)errorKey toEvent:(NSDictionary*)eventData; +- (void)sendLocalisedError:(NSString*)errorKey toRequest:(NSString*)requestId; @end diff --git a/Riot/ViewController/Widgets/WidgetViewController.m b/Riot/ViewController/Widgets/WidgetViewController.m index ddc2d04aa..d4a64b561 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.m +++ b/Riot/ViewController/Widgets/WidgetViewController.m @@ -135,7 +135,21 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', error:&error]; if (!error) { - [self onMessage:parameters]; + // Retrieve the js event payload data + NSDictionary *eventData; + MXJSONModelSetDictionary(eventData, parameters[@"event.data"]); + + NSString *requestId; + MXJSONModelSetString(requestId, eventData[@"_id"]); + + if (requestId) + { + [self onPostMessageRequest:requestId data:eventData]; + } + else + { + NSLog(@"[WidgetVC] shouldStartLoadWithRequest: ERROR: Missing request id in postMessage API %@", parameters); + } } return NO; @@ -170,33 +184,31 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', #pragma mark - postMessage API -- (void)onMessage:(NSDictionary*)JSData +- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData { - // @TODO - NSDictionary *eventData; - MXJSONModelSetDictionary(eventData, JSData[@"event.data"]); + // TODO } -- (void)sendBoolResponse:(BOOL)response toEvent:(NSDictionary*)eventData +- (void)sendBoolResponse:(BOOL)response toRequest:(NSString*)requestId { // Convert BOOL to "true" or "false" NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, - eventData[@"_id"], + requestId, response ? @"true" : @"false"]; [webView stringByEvaluatingJavaScriptFromString:js]; } -- (void)sendIntegerResponse:(NSUInteger)response toEvent:(NSDictionary*)eventData +- (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId { NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, - eventData[@"_id"], + requestId, @(response)]; [webView stringByEvaluatingJavaScriptFromString:js]; } -- (void)sendNSObjectResponse:(NSObject*)response toEvent:(NSDictionary*)eventData +- (void)sendNSObjectResponse:(NSObject*)response toRequest:(NSString*)requestId { NSString *jsString; @@ -216,15 +228,15 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', } NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, - eventData[@"_id"], + requestId, jsString]; [webView stringByEvaluatingJavaScriptFromString:js]; } -- (void)sendError:(NSString*)message toEvent:(NSDictionary*)eventData +- (void)sendError:(NSString*)message toRequest:(NSString*)requestId { - NSLog(@"[WidgetVC] sendError: Action %@ failed with message: %@", eventData[@"action"], message); + NSLog(@"[WidgetVC] sendError: Action %@ failed with message: %@", requestId, message); // TODO: JS has an additional optional parameter: nestedError [self sendNSObjectResponse:@{ @@ -232,12 +244,12 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', @"message": message } } - toEvent:eventData]; + toRequest:requestId]; } -- (void)sendLocalisedError:(NSString*)errorKey toEvent:(NSDictionary*)eventData +- (void)sendLocalisedError:(NSString*)errorKey toRequest:(NSString*)requestId { - [self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toEvent:eventData]; + [self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toRequest:requestId]; } @end From fc0dc89a14c182fb6e001cb55d6e429064da5cf3 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 15:50:13 +0200 Subject: [PATCH 7/9] Widget postMessage API: Make sure we always have a _id for postMessage request --- Riot/Assets/js/postMessageAPI.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Riot/Assets/js/postMessageAPI.js b/Riot/Assets/js/postMessageAPI.js index bc30830e3..2b145debe 100644 --- a/Riot/Assets/js/postMessageAPI.js +++ b/Riot/Assets/js/postMessageAPI.js @@ -40,6 +40,19 @@ window.riotIOS.onMessage = function(event) { event.origin = event.originalEvent.origin; } + // Use an internal "_id" field for matching onMessage events and requests + // _id was originally used by the Modular API. Keep it + if (!event.data._id) { + // The Matrix Widget API v2 spec says: + // "The requestId field should be unique and included in all requests" + event.data._id = event.data.requestId; + } + + // Make sure to have one id + if (!event.data._id) { + event.data._id = Date.now() + "-" + Math.random().toString(36); + } + // Keep this event for future usage riotIOS.events[event.data._id] = event; From 21648e785f8d0d52cdcdee66f329c561422c2f50 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 17:34:54 +0200 Subject: [PATCH 8/9] WidgetManager: Patch: Modular uses a malformed key: "stateKey" instead of "state_key" --- Riot/Utils/Widgets/WidgetManager.m | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Riot/Utils/Widgets/WidgetManager.m b/Riot/Utils/Widgets/WidgetManager.m index c004a36cf..48f753e64 100644 --- a/Riot/Utils/Widgets/WidgetManager.m +++ b/Riot/Utils/Widgets/WidgetManager.m @@ -178,7 +178,17 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; NSMutableArray *userWidgets = [NSMutableArray array]; for (NSDictionary *widgetEventContent in [mxSession.accountData accountDataForEventType:@"m.widgets"].allValues) { - MXEvent *widgetEvent = [MXEvent modelFromJSON:widgetEventContent]; + // Patch: Modular uses 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) { Widget *widget = [[Widget alloc] initWithWidgetEvent:widgetEvent inMatrixSession:mxSession]; From e7631ef184df3837e8f3c2b365ad689ebf11148b Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 17:58:18 +0200 Subject: [PATCH 9/9] Send Stickers: Plug the sticker picker widget with the room datasource to send a sticker #1860 --- Riot/AppDelegate.m | 7 ++-- .../Widgets/WidgetPickerViewController.m | 8 +++- .../Widgets/WidgetViewController.h | 7 ++++ .../Widgets/WidgetViewController.m | 39 ++++++++++++++++--- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index a3bc7075f..b11d6c6f3 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2200,7 +2200,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN sdkOptions.backgroundModeHandler = [[MXUIKitBackgroundModeHandler alloc] init]; // Get modular widget events in rooms histories - [[MXKAppSettings standardAppSettings] addSupportedEventTypes:@[kWidgetEventTypeString]]; + [[MXKAppSettings standardAppSettings] addSupportedEventTypes:@[kWidgetMatrixEventTypeString, kWidgetModularEventTypeString]]; // Disable long press on event in bubble cells [MXKRoomBubbleTableViewCell disableLongPressGestureOnEvent:YES]; @@ -2259,9 +2259,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Each room member will be considered as a potential contact. [MXKContactManager sharedManager].contactManagerMXRoomSource = MXKContactManagerMXRoomSourceAll; - // Send read receipts for modular widgets events too + // Send read receipts for widgets events too NSMutableArray *acknowledgableEventTypes = [NSMutableArray arrayWithArray:mxSession.acknowledgableEventTypes]; - [acknowledgableEventTypes addObject:kWidgetEventTypeString]; + [acknowledgableEventTypes addObject:kWidgetMatrixEventTypeString]; + [acknowledgableEventTypes addObject:kWidgetModularEventTypeString]; mxSession.acknowledgableEventTypes = acknowledgableEventTypes; } else if (mxSession.state == MXSessionStateStoreDataReady) diff --git a/Riot/ViewController/Widgets/WidgetPickerViewController.m b/Riot/ViewController/Widgets/WidgetPickerViewController.m index 958f263ff..72b4665ee 100644 --- a/Riot/ViewController/Widgets/WidgetPickerViewController.m +++ b/Riot/ViewController/Widgets/WidgetPickerViewController.m @@ -75,8 +75,12 @@ // Display the widget [widget widgetUrl:^(NSString * _Nonnull widgetUrl) { - WidgetViewController *widgetVC = [[WidgetViewController alloc] initWithUrl:widgetUrl forWidget:widget]; - [mxkViewController.navigationController pushViewController:widgetVC animated:YES]; + WidgetViewController *widgetVC = [[WidgetViewController alloc] initWithUrl:widgetUrl forWidget:widget]; + + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession]; + widgetVC.roomDataSource = [roomDataSourceManager roomDataSourceForRoom:roomId create:NO]; + + [mxkViewController.navigationController pushViewController:widgetVC animated:YES]; } failure:^(NSError * _Nonnull error) { diff --git a/Riot/ViewController/Widgets/WidgetViewController.h b/Riot/ViewController/Widgets/WidgetViewController.h index 42b0c67dd..0f9a70685 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.h +++ b/Riot/ViewController/Widgets/WidgetViewController.h @@ -17,6 +17,7 @@ #import "WebViewViewController.h" #import "WidgetManager.h" +#import "MatrixKit/MatrixKit.h" /** `WidgetViewController` displays widget within a webview. @@ -26,6 +27,12 @@ */ @interface WidgetViewController : WebViewViewController +/** + The room data source. + Required if the widget needs to post messages. + */ +@property (nonatomic) MXKRoomDataSource *roomDataSource; + /** Init 'WidgetViewController' instance with a widget. diff --git a/Riot/ViewController/Widgets/WidgetViewController.m b/Riot/ViewController/Widgets/WidgetViewController.m index d4a64b561..aad876a52 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.m +++ b/Riot/ViewController/Widgets/WidgetViewController.m @@ -18,7 +18,7 @@ #import "AppDelegate.h" -NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; +NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse('%@', %@);"; @interface WidgetViewController () { @@ -186,13 +186,42 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', - (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData { - // TODO + NSString *action; + MXJSONModelSetString(action, requestData[@"action"]); + + if ([@"m.sticker" isEqualToString:action]) + { + // Extract the sticker event content and send it as is + + // The key should be "data" according to https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing + // TODO: Fix it once spec is finalised + NSDictionary *widgetData; + NSDictionary *stickerContent; + MXJSONModelSetDictionary(widgetData, requestData[@"widgetData"]); + if (widgetData) + { + MXJSONModelSetDictionary(stickerContent, widgetData[@"content"]); + } + + if (stickerContent) + { + // Let the data source manage the sending cycle + [_roomDataSource sendEventOfType:kMXEventTypeStringSticker content:stickerContent success:nil failure:nil]; + } + else + { + NSLog(@"[WidgetVC] onPostMessageRequest: ERROR: Invalid content for m.sticker: %@", requestData); + } + + // Consider we are done with the sticker picker widget + [self withdrawViewControllerAnimated:YES completion:nil]; + } } - (void)sendBoolResponse:(BOOL)response toRequest:(NSString*)requestId { // Convert BOOL to "true" or "false" - NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI, requestId, response ? @"true" : @"false"]; @@ -201,7 +230,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', - (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId { - NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI, requestId, @(response)]; @@ -227,7 +256,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', jsString = @"null"; } - NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI, requestId, jsString];