Browse files

Make error handling explicit in response handlers. Handlers now get a…

… response object instead of a response callback. The response object has methods response.respondWith('foo')/respondWithError('bar') in js and [response respondWith:(id)responseData]/[response respondWithError:(id)error] in objc. Response handlers now receive an error object as the first argument, and a responseData object as the second argument
  • Loading branch information...
1 parent 5337155 commit d6af55b03acb52f961031fb802518106d4123253 @marcuswestin committed Oct 7, 2012
View
3 Changelog
@@ -1,8 +1,9 @@
intended v2.0.0
+ Messages are objects instead of strings. Supports NSDictionary*/Objects, NSArray*/Arrays, NSNumber*/Number & NSString*/String.
+ Messages are encoded with NSJSONSerialization. Optional fallback to JSONKit for iOS 4 support.
-+ Messages can expect responses. A message received with an expected response is accompanied by a WVJBResponseCallback/Function.
++ Messages can expect responses. A message received with an expected response is accompanied by a WVJBResponse* object.
+ Handlers can be registered by name, and called with data and an optional expected response.
++ Responses expect either an error or data (`-(void)respond:(id)data`, -(void)respondWithError:(id)error)
v0.0.1
+ ObjC: A WebViewJavascriptBridge class (a UIWebViewDelegate) that enables message passing to and from the JS
View
6 ExampleApp.xcodeproj/project.pbxproj
@@ -31,9 +31,9 @@
2CEB3ECD1602563600548120 /* ExampleApp-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ExampleApp-Prefix.pch"; sourceTree = "<group>"; };
2CEB3ECE1602563600548120 /* ExampleAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExampleAppDelegate.h; sourceTree = "<group>"; };
2CEB3ECF1602563600548120 /* ExampleAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleAppDelegate.m; sourceTree = "<group>"; };
- 2CEB3F4F16025A4E00548120 /* WebViewJavascriptBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebViewJavascriptBridge.h; sourceTree = "<group>"; };
- 2CEB3F5016025A4E00548120 /* WebViewJavascriptBridge.js.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = WebViewJavascriptBridge.js.txt; sourceTree = "<group>"; };
- 2CEB3F5116025A4E00548120 /* WebViewJavascriptBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewJavascriptBridge.m; sourceTree = "<group>"; };
+ 2CEB3F4F16025A4E00548120 /* WebViewJavascriptBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = WebViewJavascriptBridge.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
+ 2CEB3F5016025A4E00548120 /* WebViewJavascriptBridge.js.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; path = WebViewJavascriptBridge.js.txt; sourceTree = "<group>"; };
+ 2CEB3F5116025A4E00548120 /* WebViewJavascriptBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = WebViewJavascriptBridge.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
2CEB3F5416025A9000548120 /* ExampleApp.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = ExampleApp.html; sourceTree = "<group>"; };
/* End PBXFileReference section */
View
4 ExampleApp/ExampleApp.html
@@ -25,9 +25,9 @@
log('JS got a message', message)
})
- bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
+ bridge.registerHandler('testJavascriptHandler', function(data, response) {
log('JS handler testJavascriptHandler was called', data)
- responseCallback({ 'Javascript Says':'Right back atcha!' })
+ response.respondWith({ 'Javascript Says':'Right back atcha!' })
})
var button = document.getElementById('buttons').appendChild(document.createElement('button'))
View
24 ExampleApp/ExampleAppDelegate.m
@@ -3,31 +3,35 @@
@implementation ExampleAppDelegate
@synthesize window = _window;
-@synthesize javascriptBridge = _javascriptBridge;
+@synthesize javascriptBridge = _bridge;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIWebView* webView = [[UIWebView alloc] initWithFrame:self.window.bounds];
[self.window addSubview:webView];
- self.javascriptBridge = [WebViewJavascriptBridge javascriptBridgeForWebView:webView handler:^(id data, WVJBResponseCallback responseCallback) {
+ _bridge = [WebViewJavascriptBridge javascriptBridgeForWebView:webView handler:^(id data, WVJBResponse *response) {
NSLog(@"ObjC received message from JS: %@", data);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"ObjC got message from Javascript:" message:data delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
}];
- [self.javascriptBridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
+ [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponse *response) {
NSLog(@"testObjcCallback called: %@", data);
- responseCallback(@"Response from testObjcCallback");
+ [response respondWith:@"Response from testObjcCallback"];
}];
- [self.javascriptBridge send:@"A string sent from ObjC before Webview has loaded."];
- [self.javascriptBridge callHandler:@"testJavascriptHandler" data:[NSDictionary dictionaryWithObject:@"before ready" forKey:@"foo"]];
+ [_bridge send:@"A string sent from ObjC before Webview has loaded." responseCallback:^(id error, id responseData) {
+ if (error) { return NSLog(@"Uh oh - I got an error: %@", error); }
+ NSLog(@"objc got response! %@ %@", error, responseData);
+ }];
+
+ [_bridge callHandler:@"testJavascriptHandler" data:[NSDictionary dictionaryWithObject:@"before ready" forKey:@"foo"]];
[self renderButtons:webView];
[self loadExamplePage:webView];
- [self.javascriptBridge send:@"A string sent from ObjC after Webview has loaded."];
+ [_bridge send:@"A string sent from ObjC after Webview has loaded."];
[self.window makeKeyAndVisible];
return YES;
@@ -48,12 +52,12 @@ - (void)renderButtons:(UIWebView*)webView {
}
- (void)sendMessage:(id)sender {
- [self.javascriptBridge send:@"A string sent from ObjC to JS"];
+ [_bridge send:@"A string sent from ObjC to JS"];
}
- (void)callHandler:(id)sender {
- [self.javascriptBridge callHandler:@"testJavascriptHandler" data:[NSDictionary dictionaryWithObject:@"Hi there, JS!" forKey:@"greetingFromObjC"] responseCallback:^(id responseData) {
- NSLog(@"testJavascriptHandler responded: %@", responseData);
+ [_bridge callHandler:@"testJavascriptHandler" data:[NSDictionary dictionaryWithObject:@"Hi there, JS!" forKey:@"greetingFromObjC"] responseCallback:^(id error, id response) {
+ NSLog(@"testJavascriptHandler responded: %@ %@", error, response);
}];
}
View
75 README.md
@@ -21,29 +21,35 @@ To use a WebViewJavascriptBridge in your own project:
3) Instantiate a UIWebView and a WebViewJavascriptBridge:
UIWebView* webView = [[UIWebView alloc] initWithFrame:self.window.bounds];
- WebViewJavascriptBridge* javascriptBridge = [WebViewJavascriptBridge javascriptBridgeForWebView:webView handler:^(id data, WVJBResponseCallback callback) {
+ WebViewJavascriptBridge* javascriptBridge = [WebViewJavascriptBridge javascriptBridgeForWebView:webView handler:^(id data, WVJBResponse* response) {
NSLog(@"Received message from javascript: %@", data);
+ [response respondWith:@"Right back atcha"];
+ // or [response respondWithError:]
}];
4) Go ahead and send some messages from ObjC to javascript:
[javascriptBridge send:@"Well hello there"];
[javascriptBridge send:[NSDictionary dictionaryWithObject:@"Foo" forKey:@"Bar"]];
- [javascriptBridge send:@"Give me a response, will you?" responseCallback:^(id responseData) {
- NSLog(@"I got a response! %@", responseData);
+ [javascriptBridge send:@"Give me a response, will you?" responseCallback:^(id error, id responseData) {
+ NSLog(@"objc got its response! %@ %@", error, responseData);
}];
4) Finally, set up the javascript side:
document.addEventListener('WebViewJavascriptBridgeReady', function onBridgeReady(event) {
var bridge = event.bridge
- bridge.init(function(message, responseCallback) {
+ bridge.init(function(message, response) {
alert('Received message: ' + message)
- if (responseCallback) {
- responseCallback("Right back atcha")
+ if (response) {
+ response.respondWith("Right back atcha")
+ // or use response.respondWithError("Booh!")
}
})
bridge.send('Hello from the javascript')
+ bridge.send('Please respond to this', function responseCallback(error, responseData) {
+ console.log("javascript got its response", error, responseData)
+ })
}, false)
API Reference
@@ -56,21 +62,20 @@ API Reference
Create a javascript bridge for the given UIWebView.
+The `WVJBResponse` will not be `nil` if the javascript expects a response.
+
+Optionally, pass in `webViewDelegate:(UIWebViewDelegate*)webViewDelegate` if you need to respond to the [UIWebView's lifecycle events](http://developer.apple.com/library/ios/documentation/uikit/reference/UIWebViewDelegate_Protocol/Reference/Reference.html).
+
Example:
- [WebViewJavascriptBridge javascriptBridgeForWebView:webView handler:^(id data, WVJBResponseCallback responseCallback) {
+ [WebViewJavascriptBridge javascriptBridgeForWebView:webView handler:^(id data, WVJBResponse response) {
NSLog(@"Received message from javascript: %@", data);
- if (responseCallback) {
- responseCallback(@"Right back atcha")
+ if (response) {
+ [response respondWith:@"Right back atcha"];
}
}]
- [WebViewJavascriptBridge javascriptBridgeForWebView:webView webViewDelegate:self handler:^(id data, WVJBCallback responseCallback) { /* ... */ }];
-
-The handler's `responseCallback` will be a block if javascript sent the message with a function responseCallback, or `nil` otherwise.
-
-Optionally, pass in `webViewDelegate:(UIWebViewDelegate*)webViewDelegate` if you need to respond to the [UIWebView's lifecycle events](http://developer.apple.com/library/ios/documentation/uikit/reference/UIWebViewDelegate_Protocol/Reference/Reference.html).
-
+ [WebViewJavascriptBridge javascriptBridgeForWebView:webView webViewDelegate:self handler:^(id data, WVJBResponse response) { /* ... */ }];
##### `[bridge send:(id)data]`
##### `[bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback]`
@@ -81,18 +86,19 @@ Example:
[bridge send:@"Hi"];
[bridge send:[NSDictionary dictionaryWithObject:@"Foo" forKey:@"Bar"]];
- [bridge send:@"I expect a response!" responseCallback:^(id data) {
- NSLog(@"Got response: %@", data);
+ [bridge send:@"I expect a response!" responseCallback:^(id error, id responseData) {
+ if (error) { return NSLog(@"Uh oh, I got an error: %@", error); }
+ NSLog(@"Got response! %@", responseData);
}];
##### `[bridge registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler]`
-Register a handler called `handlerName`. The javascript can then call this handler with `WebViewJavascriptBridge.callHandler("handlerName", function(response) { ... })`.
+Register a handler called `handlerName`. The javascript can then call this handler with `WebViewJavascriptBridge.callHandler("handlerName")`.
Example:
- [bridge registerHandler:@"getScreenHeight" handler:^(id data, WVJBResponseCallback responseCallback) {
- responseCallback([NSNumber numberWithInt:[UIScreen mainScreen].bounds.size.height]);
+ [bridge registerHandler:@"getScreenHeight" handler:^(id data, WVJBResponse response) {
+ [response respondWith:[NSNumber numberWithInt:[UIScreen mainScreen].bounds.size.height]];
}];
##### `[bridge callHandler:(NSString*)handlerName data:(id)data]`
@@ -103,7 +109,8 @@ Call the javascript handler called `handlerName`. Optionally expect a response b
Example:
[bridge callHandler:@"showAlert" data:@"Hi from ObjC to JS!"];
- [bridge callHandler:@"getCurrentPageUrl" data:nil responseCallback:^(id responseData) {
+ [bridge callHandler:@"getCurrentPageUrl" data:nil responseCallback:^(id error, id responseData) {
+ if (error) { return NSLog(@"Huston, we got a problem: %@", error); }
NSLog(@"Current UIWebView page URL is: %@", responseData);
}];
@@ -121,45 +128,47 @@ Example:
// Start using the bridge
}, false)
-##### `bridge.init(function messageHandler(data, responseCallback) { ... })`
+##### `bridge.init(function messageHandler(data, response) { ... })`
Initialize the bridge. This should be called inside of the `'WebViewJavascriptBridgeReady'` event handler.
The `messageHandler` function will receive all messages sent from ObjC via `[bridge send:(id)data]` and `[bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback]`.
-The `responseCallback` will be a function if ObjC sent the message with a `WVJBResponseCallback` block, or `undefined` otherwise.
+The `response` object will be defined if if ObjC sent the message with a `WVJBResponseCallback` block.
Example:
- bridge.init(function(data, responseCallback) {
+ bridge.init(function(data, response) {
alert("Got data " + JSON.stringify(data))
- if (responseCallback) {
- responseCallback("Right back atcha!")
+ if (response) {
+ response.respondWith("Right back atcha!")
+ // or, response.respondWithError("It went wrong!")
}
})
##### `bridge.send("Hi there!")`
##### `bridge.send({ Foo:"Bar" })`
-##### `bridge.send(data, function responseCallback(responseData) { ... })`
+##### `bridge.send(data, function responseCallback(error, responseData) { ... })`
Send a message to ObjC. Optionally expect a response by giving a `responseCallback` function.
Example:
bridge.send("Hi there!")
- bridge.send("Hi there!", function(response) {
- alert("I got a response! "+JSON.stringify(response))
+ bridge.send("Hi there!", function(error, responseData) {
+ if (error) { return alert("Uh oh, we got an error: "+error) }
+ alert("I got a response! "+JSON.stringify(data))
})
-##### `bridge.registerHandler("handlerName", function(data, responseCallback) { ... })`
+##### `bridge.registerHandler("handlerName", function(error, responseData) { ... })`
-Register a handler called `handlerName`. The ObjC can then call this handler with `[bridge callHandler:"handlerName" data:@"Foo"]` and `[bridge callHandler:"handlerName" data:@"Foo" responseCallback:^(id responseData) { ... }]`
+Register a handler called `handlerName`. The ObjC can then call this handler with `[bridge callHandler:"handlerName" data:@"Foo"]` and `[bridge callHandler:"handlerName" data:@"Foo" responseCallback:^(id error, id responseData) { ... }]`
Example:
bridge.registerHandler("showAlert", function(data) { alert(data) })
- bridge.registerHandler("getCurrentPageUrl", function(data, responseCallback) {
- responseCallback(document.location.toString())
+ bridge.registerHandler("getCurrentPageUrl", function(data, response) {
+ response.respondWith(document.location.toString())
})
View
16 WebViewJavascriptBridge/WebViewJavascriptBridge.h
@@ -1,22 +1,22 @@
#import <UIKit/UIKit.h>
-typedef void (^WVJBResponseCallback)(id responseData);
-typedef void (^WVJBHandler)(id data, WVJBResponseCallback responseCallback);
-
-@class WebViewJavascriptBridge;
+@class WVJBResponse;
+typedef void (^WVJBResponseCallback)(id error, id responseData);
+typedef void (^WVJBHandler)(id data, WVJBResponse* response);
@interface WebViewJavascriptBridge : NSObject <UIWebViewDelegate>
-
+ (id)javascriptBridgeForWebView:(UIWebView*)webView handler:(WVJBHandler)handler;
+ (id)javascriptBridgeForWebView:(UIWebView*)webView webViewDelegate:(id <UIWebViewDelegate>)webViewDelegate handler:(WVJBHandler)handler;
-
- (void)send:(id)message;
- (void)send:(id)message responseCallback:(WVJBResponseCallback)responseCallback;
-
- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;
-
- (void)callHandler:(NSString*)handlerName;
- (void)callHandler:(NSString*)handlerName data:(id)data;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;
+@end
+@interface WVJBResponse : NSObject
+- (WVJBResponse*) initWithCallbackId:(NSString*)callbackId bridge:(WebViewJavascriptBridge*)bridge;
+- (void) respondWith:(id)responseData;
+- (void) respondWithError:(id)error;
@end
View
41 WebViewJavascriptBridge/WebViewJavascriptBridge.js.txt
@@ -56,30 +56,39 @@
return messageQueueString
}
- function _createResponseCallback(responseId) {
- return function(data) {
- _doSend({ responseId:responseId, data:data })
- }
- }
-
function _dispatchMessageFromObjC(messageJSON) {
setTimeout(function _timeoutDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON)
var messageHandler
if (message.responseId) {
- handler = responseCallbacks[message.responseId]
+ var responseCallback = responseCallbacks[message.responseId]
+ responseCallback(message.error, message.responseData)
delete responseCallbacks[message.responseId]
- } else if (message.handlerName) {
- handler = messageHandlers[message.handlerName]
- } else {
- handler = WebViewJavascriptBridge._messageHandler
- }
-
- if (message.callbackId) {
- handler(message.data, _createResponseCallback(message.callbackId))
} else {
- handler(message.data)
+ var response
+ if (message.callbackId) {
+ var callbackResponseId = message.callbackId
+ response = {
+ respondWith: function(data) {
+ _doSend({ responseId:callbackResponseId, data:data })
+ },
+ respondWithError: function(error) {
+ _doSend({ responseId:callbackResponseId, data:data })
+ }
+ }
+ }
+
+ var handler = WebViewJavascriptBridge._messageHandler
+ if (message.handlerName) {
+ handler = messageHandlers[message.handlerName]
+ }
+
+ try {
+ handler(message.data, response)
+ } catch(exception) {
+ console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
+ }
}
})
}
View
61 WebViewJavascriptBridge/WebViewJavascriptBridge.m
@@ -109,31 +109,31 @@ - (void)_flushMessageQueue {
NSArray* messages = [messageQueueString componentsSeparatedByString:MESSAGE_SEPARATOR];
for (NSString *messageJSON in messages) {
NSDictionary* message = [self _deserializeMessageJSON:messageJSON];
- WVJBResponseCallback responseCallback = NULL;
- if ([message objectForKey:@"callbackId"]) {
- __block NSString* responseId = [message objectForKey:@"callbackId"];
- responseCallback = ^(NSDictionary* data) {
- NSDictionary* responseMessage = [NSDictionary dictionaryWithObjectsAndKeys: responseId, @"responseId", data, @"data", nil];
- [self _queueMessage:responseMessage];
- };
- }
-
- WVJBHandler handler = self.messageHandler;
- if ([message objectForKey:@"handlerName"]) {
- handler = [self.messageHandlers objectForKey:[message objectForKey:@"handlerName"]];
- } else if ([message objectForKey:@"responseId"]) {
- handler = [self.responseCallbacks objectForKey:[message objectForKey:@"responseId"]];
- }
- if (handler) {
+ NSString* responseId = [message objectForKey:@"responseId"];
+ if (responseId) {
+ WVJBResponseCallback responseCallback = [_responseCallbacks objectForKey:responseId];
+ responseCallback([message objectForKey:@"error"], [message objectForKey:@"data"]);
+ [_responseCallbacks removeObjectForKey:responseId];
+ } else {
+ WVJBResponse* response = nil;
+ if ([message objectForKey:@"callbackId"]) {
+ response = [[WVJBResponse alloc] initWithCallbackId:[message objectForKey:@"callbackId"] bridge:self];
+ }
+
+ WVJBHandler handler = self.messageHandler;
+
+ NSString* handlerName = [message objectForKey:@"handlerName"];
+ if (handlerName) {
+ handler = [_messageHandlers objectForKey:handlerName];
+ }
+
@try {
- handler([message objectForKey:@"data"], responseCallback);
+ handler([message objectForKey:@"data"], response);
}
@catch (NSException *exception) {
- NSLog(@"WebViewJavascriptBridge: WARNING: handler threw. %@ %@", message, exception);
+ NSLog(@"WebViewJavascriptBridge: WARNING: objc handler threw. %@ %@", message, exception);
}
- } else {
- NSLog(@"WebViewJavascriptBridge: WARNING: handler not found (%@)", [message objectForKey:@"handlerName"]);
}
}
}
@@ -209,3 +209,24 @@ - (void)webViewDidStartLoad:(UIWebView *)webView {
}
@end
+
+@implementation WVJBResponse {
+ NSString* _responseId;
+ WebViewJavascriptBridge* _bridge;
+}
+- (WVJBResponse*) initWithCallbackId:(NSString*)callbackId bridge:(WebViewJavascriptBridge*)bridge {
+ if (self = [super init]) {
+ _responseId = callbackId;
+ _bridge = bridge;
+ }
+ return self;
+}
+- (void) respondWith:(id)responseData {
+ NSDictionary* message = [NSDictionary dictionaryWithObjectsAndKeys: _responseId, @"responseId", responseData, @"responseData", nil];
+ [_bridge _queueMessage:message];
+}
+- (void) respondWithError:(id)error {
+ NSDictionary* message = [NSDictionary dictionaryWithObjectsAndKeys: _responseId, @"responseId", error, @"error", nil];
+ [_bridge _queueMessage:message];
+}
+@end

0 comments on commit d6af55b

Please sign in to comment.