Skip to content

Commit

Permalink
Added JavascriptWebViewBridgeBase to WKWebViewJavascriptBridge
Browse files Browse the repository at this point in the history
  • Loading branch information
lokimeyburg committed Oct 30, 2014
1 parent 95779d0 commit 859c823
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 181 deletions.
8 changes: 7 additions & 1 deletion Example Apps/ExampleApp-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
0E50601C1A01B442000BEEEA /* WebViewJavascriptBridgeBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E50601B1A01B442000BEEEA /* WebViewJavascriptBridgeBase.m */; };
0E8082DB19EDC32300479452 /* WKWebViewJavascriptBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E8082DA19EDC32300479452 /* WKWebViewJavascriptBridge.m */; };
2C1562B5176B9F8400B4AE50 /* WebViewJavascriptBridge.js.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2C1562B4176B9F8400B4AE50 /* WebViewJavascriptBridge.js.txt */; };
2C1562C0176BA63500B4AE50 /* WebViewJavascriptBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C1562A9176B9F6200B4AE50 /* WebViewJavascriptBridge.m */; };
Expand All @@ -22,6 +23,8 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
0E50601B1A01B442000BEEEA /* WebViewJavascriptBridgeBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewJavascriptBridgeBase.m; sourceTree = "<group>"; };
0E50601D1A01B44C000BEEEA /* WebViewJavascriptBridgeBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebViewJavascriptBridgeBase.h; sourceTree = "<group>"; };
0E8082D919EDC32300479452 /* WKWebViewJavascriptBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKWebViewJavascriptBridge.h; sourceTree = "<group>"; };
0E8082DA19EDC32300479452 /* WKWebViewJavascriptBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WKWebViewJavascriptBridge.m; sourceTree = "<group>"; };
0E8082DC19EDD98700479452 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -64,8 +67,10 @@
2C1562B4176B9F8400B4AE50 /* WebViewJavascriptBridge.js.txt */,
2C1562A8176B9F6200B4AE50 /* WebViewJavascriptBridge.h */,
2C1562A9176B9F6200B4AE50 /* WebViewJavascriptBridge.m */,
0E8082D919EDC32300479452 /* WKWebViewJavascriptBridge.h */,
0E8082DA19EDC32300479452 /* WKWebViewJavascriptBridge.m */,
0E8082D919EDC32300479452 /* WKWebViewJavascriptBridge.h */,
0E50601B1A01B442000BEEEA /* WebViewJavascriptBridgeBase.m */,
0E50601D1A01B44C000BEEEA /* WebViewJavascriptBridgeBase.h */,
);
name = WebViewJavascriptBridge;
path = ../../WebViewJavascriptBridge;
Expand Down Expand Up @@ -194,6 +199,7 @@
0E8082DB19EDC32300479452 /* WKWebViewJavascriptBridge.m in Sources */,
2C45CA2C1884AD520002A4E2 /* ExampleAppViewController.m in Sources */,
2CA045C217117439006DEE8B /* ExampleAppDelegate.m in Sources */,
0E50601C1A01B442000BEEEA /* WebViewJavascriptBridgeBase.m in Sources */,
2CA045C317117439006DEE8B /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
2 changes: 0 additions & 2 deletions WebViewJavascriptBridge/WKWebViewJavascriptBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#if defined(supportsWKWebKit )

#import <Foundation/Foundation.h>
#define kCustomProtocolScheme @"wvjbscheme"
#define kQueueHasMessage @"__WVJB_QUEUE_MESSAGE__"

#import <WebKit/WebKit.h>
typedef void (^WVJBResponseCallback)(id responseData);
Expand Down
201 changes: 23 additions & 178 deletions WebViewJavascriptBridge/WKWebViewJavascriptBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,21 @@


#import "WKWebViewJavascriptBridge.h"
#import "WebViewJavascriptBridgeBase.h"

#if defined(supportsWKWebKit)

typedef NSDictionary WVJBMessage;

@implementation WKWebViewJavascriptBridge {
WKWebView* _webView;
id _webViewDelegate;
NSMutableArray* _startupMessageQueue;
NSMutableDictionary* _responseCallbacks;
NSMutableDictionary* _messageHandlers;
long _uniqueId;
WVJBHandler _messageHandler;
NSBundle *_resourceBundle;
NSUInteger _numRequestsLoading;
WebViewJavascriptBridgeBase *_base;
}

/* API
*****/

static bool logging = false;
+ (void)enableLogging { logging = true; }
+ (void)enableLogging { [WebViewJavascriptBridgeBase enableLogging]; }

+ (instancetype)bridgeForWebView:(WKWebView*)webView handler:(WVJBHandler)handler {
return [self bridgeForWebView:webView webViewDelegate:nil handler:handler];
Expand All @@ -51,7 +44,7 @@ - (void)send:(id)data {
}

- (void)send:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
[self _sendData:data responseCallback:responseCallback handlerName:nil];
[_base _sendData:data responseCallback:responseCallback handlerName:nil];
}

- (void)callHandler:(NSString *)handlerName {
Expand All @@ -63,205 +56,57 @@ - (void)callHandler:(NSString *)handlerName data:(id)data {
}

- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
[self _sendData:data responseCallback:responseCallback handlerName:handlerName];
[_base _sendData:data responseCallback:responseCallback handlerName:handlerName];
}

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_messageHandlers[handlerName] = [handler copy];
_base.messageHandlers[handlerName] = [handler copy];
}

- (void)reset {
_startupMessageQueue = [NSMutableArray array];
_responseCallbacks = [NSMutableDictionary dictionary];
_uniqueId = 0;
[_base reset];
}

/* Internals
***********/

- (void)dealloc {
[self _platformSpecificDealloc];

_base = nil;
_webView = nil;
_webViewDelegate = nil;
_startupMessageQueue = nil;
_responseCallbacks = nil;
_messageHandlers = nil;
_messageHandler = nil;
}

- (void)_sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
NSMutableDictionary* message = [NSMutableDictionary dictionary];

if (data) {
message[@"data"] = data;
}

if (responseCallback) {
NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
_responseCallbacks[callbackId] = [responseCallback copy];
message[@"callbackId"] = callbackId;
}

if (handlerName) {
message[@"handlerName"] = handlerName;
}
[self _queueMessage:message];
}

- (void)_queueMessage:(WVJBMessage*)message {
if (_startupMessageQueue) {
[_startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}

- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self _serializeMessage:message];
[self _log:@"SEND" json:messageJSON];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];

NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
if ([[NSThread currentThread] isMainThread]) {
[_webView evaluateJavaScript:javascriptCommand completionHandler:nil];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[_webView evaluateJavaScript:javascriptCommand completionHandler:nil];
});
}
}

- (void)_flushMessageQueue:(NSString *)messageQueueString{
id messages = [self _deserializeMessageJSON:messageQueueString];
if (![messages isKindOfClass:[NSArray class]]) {
NSLog(@"WKWebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messages class], messages);
return;
}
for (WVJBMessage* message in messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WKWebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
continue;
}
[self _log:@"RCVD" json:message];

NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[_responseCallbacks removeObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}

WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}

WVJBHandler handler;
if (message[@"handlerName"]) {
handler = _messageHandlers[message[@"handlerName"]];
} else {
handler = _messageHandler;
}

if (!handler) {
[NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];
}

handler(message[@"data"], responseCallback);
}
}
}

- (NSString *)_serializeMessage:(id)message {
return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:message options:0 error:nil] encoding:NSUTF8StringEncoding];
}

- (NSArray*)_deserializeMessageJSON:(NSString *)messageJSON {
return [NSJSONSerialization JSONObjectWithData:[messageJSON dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
}

- (void)_log:(NSString *)action json:(id)json {
if (!logging) { return; }
if (![json isKindOfClass:[NSString class]]) {
json = [self _serializeMessage:json];
}
if ([json length] > 500) {
NSLog(@"WVJB %@: %@ [...]", action, [json substringToIndex:500]);
} else {
NSLog(@"WVJB %@: %@", action, json);
}
_webView.navigationDelegate = nil;
}




/* WKWebView Specific Internals
******************************/

- (void) _platformSpecificSetup:(WKWebView*)webView webViewDelegate:(id<WKNavigationDelegate>)webViewDelegate handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle{
_messageHandler = messageHandler;
_webView = webView;
_webViewDelegate = webViewDelegate;
_messageHandlers = [NSMutableDictionary dictionary];
_webView.navigationDelegate = self;
_resourceBundle = bundle;
}

- (void) _platformSpecificDealloc {
_webView.navigationDelegate = nil;
_base = [[WebViewJavascriptBridgeBase alloc] initWithWebViewType:@"WKWebView" handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle];
}


- (void)WKFlushMessageQueue {
[_webView evaluateJavaScript:@"WebViewJavascriptBridge._fetchQueue();" completionHandler:^(NSString* result, NSError* error) {
[self _flushMessageQueue:result];
[_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {
[_base _flushMessageQueue:result];
}];
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
if (webView != _webView) { return; }

_base.numRequestsLoading--;

_numRequestsLoading--;

if (_numRequestsLoading == 0) {
[webView evaluateJavaScript:@"typeof WebViewJavascriptBridge == \'object\';" completionHandler:^(NSString *result, NSError *error) {
if(![result boolValue]){
NSBundle *bundle = _resourceBundle ? _resourceBundle : [NSBundle mainBundle];
NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"];
NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
[webView evaluateJavaScript:js completionHandler:nil];

if (_startupMessageQueue) {
for (id queuedMessage in _startupMessageQueue) {
[self _dispatchMessage:queuedMessage];
}
_startupMessageQueue = nil;
}
}
if (_base.numRequestsLoading == 0) {
[webView evaluateJavaScript:[_base webViewJavascriptCheckCommand] completionHandler:^(NSString *result, NSError *error) {
[_base injectJavascriptFile:[result boolValue]];
}];
}


__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:didFinishNavigation:)]) {
[strongDelegate webView:webView didFinishNavigation:navigation];
Expand All @@ -275,11 +120,12 @@ - (void)webView:(WKWebView *)webView
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if ([[url scheme] isEqualToString:kCustomProtocolScheme]) {
if ([[url host] isEqualToString:kQueueHasMessage]) {

if ([_base correctProcotocolScheme:url]) {
if ([_base correctHost:url]) {
[self WKFlushMessageQueue];
} else {
NSLog(@"WKWebViewJavascriptBridge: WARNING: Received unknown WKWebViewJavascriptBridge command %@://%@", kCustomProtocolScheme, [url path]);
[_base logUnkownMessage:url];
}
[webView stopLoading];
}
Expand All @@ -294,7 +140,7 @@ - (void)webView:(WKWebView *)webView
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
if (webView != _webView) { return; }

_numRequestsLoading++;
_base.numRequestsLoading++;

__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:didStartProvisionalNavigation:)]) {
Expand All @@ -306,10 +152,9 @@ - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation
- (void)webView:(WKWebView *)webView
didFailNavigation:(WKNavigation *)navigation
withError:(NSError *)error {

if (webView != _webView) { return; }

_numRequestsLoading--;
_base.numRequestsLoading--;

__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:didFailNavigation:withError:)]) {
Expand Down
49 changes: 49 additions & 0 deletions WebViewJavascriptBridge/WebViewJavascriptBridgeBase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// WebViewJavascriptBridgeBase.h
// ExampleApp-iOS
//
// Created by Loki Meyburg on 2014-10-29.
// Copyright (c) 2014 Marcus Westin. All rights reserved.
//
#import <Foundation/Foundation.h>

#define kCustomProtocolScheme @"wvjbscheme"
#define kQueueHasMessage @"__WVJB_QUEUE_MESSAGE__"

typedef void (^WVJBResponseCallback)(id responseData);
typedef void (^WVJBHandler)(id data, WVJBResponseCallback responseCallback);
typedef NSDictionary WVJBMessage;

@interface WebViewJavascriptBridgeBase : NSObject

@property (strong, nonatomic) NSMutableArray* startupMessageQueue;
@property (strong, nonatomic) NSMutableDictionary* responseCallbacks;
@property (strong, nonatomic) NSMutableDictionary* messageHandlers;
@property (strong, nonatomic) WVJBHandler messageHandler;
@property NSUInteger numRequestsLoading;


+ (void)enableLogging;
-(id)initWithWebViewType:(NSString*)webViewType handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle;
-(void)reset;
- (void)_sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName;
- (void)_queueMessage:(WVJBMessage*)message;
- (void)_dispatchMessage:(WVJBMessage*)message;
- (void)_flushMessageQueue:(NSString *)messageQueueString;

// specific extractions
- (void) injectJavascriptFile:(BOOL)shouldInject;
-(BOOL) correctProcotocolScheme:(NSURL*)url;
-(BOOL) correctHost:(NSURL*)urll;
-(void) logUnkownMessage:(NSURL*)url;
-(NSString *) webViewJavascriptCheckCommand;
-(NSString *) webViewJavascriptFetchQueyCommand;


// probably dont need to be public
- (NSString *)_serializeMessage:(id)message;
- (NSArray*)_deserializeMessageJSON:(NSString *)messageJSON;
- (void)_log:(NSString *)action json:(id)json;


@end
Loading

0 comments on commit 859c823

Please sign in to comment.