From 169145153db3f6047092afed6c44d6b92bcb9750 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 7 May 2018 14:18:24 +0200 Subject: [PATCH] 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