Code Monkey home page Code Monkey logo

karosli / kkjsbridge Goto Github PK

View Code? Open in Web Editor NEW
679.0 14.0 115.0 10.54 MB

一站式解决 WKWebView 支持离线包,Ajax/Fetch 请求,表单请求和 Cookie 同步的问题 (基于 Ajax Hook,Fetch Hook 和 Cookie Hook)

License: MIT License

Ruby 0.47% Objective-C 50.19% JavaScript 32.77% TypeScript 9.36% Python 1.21% HTML 4.83% Shell 1.17%
wkwebview cookie ajax-hook cookie-hook wkwebview-reuse jsapi-module-based wkwebview-ajax-cookie jsapi formdata fetch-hook

kkjsbridge's Introduction

KKJSBridge

GitHub stars GitHub forks GitHub issues GitHub license

一站式解决 WKWebView 支持离线包,Ajax/Fetch 请求和 Cookie 同步的问题 (基于 Ajax Hook,Fetch Hook 和 Cookie Hook)

更详细的介绍

KKJSBridge 支持的功能

  • JSBrdige 相关

    • 基于 MessageHandler 搭建通信层

    • 支持模块化的管理 JSAPI

    • 支持模块共享上下文信息

    • 支持模块消息转发

    • 支持 JSBridge 同步调用

    • 兼容 WebViewJavascriptBridge

  • 请求相关

    • 支持离线资源

    • 支持 ajax/fetch hook 避免 body 丢失

    • 支持 ajax/fetch 同步请求

    • Native 侧控制 ajax/fetch hook

    • 支持表单数据,支持图片上传

    • 支持 ajax/fetch 请求外部代理

    • 分别提供了 ajax hook 和 ajax urlprotocol hook 两种方案,可以根据具体需求自由选择

  • WebView 相关

    • Cookie 统一管理

    • WKWebView 复用

Demo

demo 概览

模块化调用 JSAPI

模块化调用 JSAPI

模块化调用 JSAPI

ajax hook 演示

ajax hook 演示

淘宝 ajax hook 演示

淘宝 ajax hook 演示

ajax 发送表单 演示

淘宝 ajax hook 演示

用法

从复用池取出缓存的 WKWebView,并开启 ajax hook

+ (void)load {
    __block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        [self prepareWebView];
        [[NSNotificationCenter defaultCenter] removeObserver:observer];
    }];
}

+ (void)prepareWebView {
    // 预先缓存一个 webView
    [KKWebView configCustomUAWithType:KKWebViewConfigUATypeAppend UAString:@"KKJSBridge/1.0.0"];
    [[KKWebViewPool sharedInstance] makeWebViewConfiguration:^(WKWebViewConfiguration * _Nonnull configuration) {
        // 必须前置配置,否则会造成属性不生效的问题
        configuration.allowsInlineMediaPlayback = YES;
        configuration.preferences.minimumFontSize = 12;
    }];
    [[KKWebViewPool sharedInstance] enqueueWebViewWithClass:KKWebView.class];
    KKJSBridgeConfig.ajaxDelegateManager = (id<KKJSBridgeAjaxDelegateManager>)self; // 请求外部代理处理,可以借助 AFN 网络库来发送请求
}

- (void)dealloc {
    // 回收到复用池
    [[KKWebViewPool sharedInstance] enqueueWebView:self.webView];
}

- (void)commonInit {
    _webView = [[KKWebViewPool sharedInstance] dequeueWebViewWithClass:KKWebView.class webViewHolder:self];
    _webView.navigationDelegate = self;
    _jsBridgeEngine = [KKJSBridgeEngine bridgeForWebView:self.webView];
    _jsBridgeEngine.config.enableAjaxHook = YES; // 开启 ajax hook

    [self registerModule];
}

#pragma mark - KKJSBridgeAjaxDelegateManager
+ (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request callbackDelegate:(NSObject<KKJSBridgeAjaxDelegate> *)callbackDelegate {
    return [[self ajaxSesstionManager] dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        // 处理响应数据
        [callbackDelegate JSBridgeAjax:callbackDelegate didReceiveResponse:response];
        if ([responseObject isKindOfClass:NSData.class]) {
            [callbackDelegate JSBridgeAjax:callbackDelegate didReceiveData:responseObject];
        } else if ([responseObject isKindOfClass:NSDictionary.class]) {
            NSData *responseData = [NSJSONSerialization dataWithJSONObject:responseObject options:0 error:nil];
            [callbackDelegate JSBridgeAjax:callbackDelegate didReceiveData:responseData];
        } else {
            NSData *responseData = [NSJSONSerialization dataWithJSONObject:@{} options:0 error:nil];
            [callbackDelegate JSBridgeAjax:callbackDelegate didReceiveData:responseData];
        }
        [callbackDelegate JSBridgeAjax:callbackDelegate didCompleteWithError:error];
    }];
}

注册模块

- (void)registerModule {
 ModuleContext *context = [ModuleContext new];
 context.vc = self;
 context.scrollView = self.webView.scrollView;
 context.name = @"上下文";
 // 注册 默认模块
 [self.jsBridgeEngine.moduleRegister registerModuleClass:ModuleDefault.class];
 // 注册 模块A
 [self.jsBridgeEngine.moduleRegister registerModuleClass:ModuleA.class];
 // 注册 模块B 并带入上下文
 [self.jsBridgeEngine.moduleRegister registerModuleClass:ModuleB.class withContext:context];
 // 注册 模块C
 [self.jsBridgeEngine.moduleRegister registerModuleClass:ModuleC.class];
}

模块定义

@interface ModuleB()<KKJSBridgeModule>

@property (nonatomic, weak) ModuleContext *context;

@end

@implementation ModuleB

// 模块名称
+ (nonnull NSString *)moduleName {
    return @"b";
}

// 单例模块
+ (BOOL)isSingleton {
    return YES;
}

// 模块初始化方法,支持上下文带入
- (instancetype)initWithEngine:(KKJSBridgeEngine *)engine context:(id)context {
    if (self = [super init]) {
        _context = context;
        NSLog(@"ModuleB 初始化并带上 %@", self.context.name);
    }

    return self;
}

// 模块提供的方法
- (void)callToGetVCTitle:(KKJSBridgeEngine *)engine params:(NSDictionary *)params responseCallback:(void (^)(NSDictionary *responseData))responseCallback {
    responseCallback ? responseCallback(@{@"title": self.context.vc.navigationItem.title ? self.context.vc.navigationItem.title : @""}) : nil;
}

JS 侧调用方式

// 异步调用
window.KKJSBridge.call('b', 'callToGetVCTitle', {}, function(res) {
    console.log('receive vc title:', res.title);
});

// 同步调用
var res = window.KKJSBridge.syncCall('b', 'callToGetVCTitle', {});
console.log('receive vc title:', res.title);

安装

  1. CocoaPods

    //In Podfile
    
    # 分别提供了 ajax hook 和 ajax urlprotocol hook 两种方案,可以根据具体需求自由选择。
    # 只能选择其中一个方案,默认是 ajax protocol hook。
    pod 'KKJSBridge/AjaxProtocolHook'
    pod 'KKJSBridge/AjaxHook'
    

Ajax Hook 方案对比

这里只对比方案间相互比较的优缺点,共同的优点,就不赘述了。如果对私有 API 不敏感的,我是比较推荐使用方案一的。

方案一:Ajax Hook 部分 API + NSURLProtocol

这个方案对应的是这里的 KKJSBridge/AjaxProtocolHook

原理介绍:此种方案,是只需要 hook ajax 中的 open/send 方法,在 hook 的 send 方法里,先把要发送的 body 通过 JSBridge 发送到 Native 侧去缓存起来。缓存成功后,再去执行真实的 send 方法,NSURLProtocol 此时会拦截到该请求,然后取出之前缓存的 body 数据,重新拼接请求,就可以发送出去了,然后通过 NSURLProtocol 把请求结果返回给 WebView 内核。

优点:

  • 兼容性会更好,网络请求都是走 webview 原生的方式。
  • hook 的逻辑会更少,会更加稳定。
  • 可以更好的支持 ajax 获取二进制的数据。例如 H5 小游戏场景(白鹭引擎是通过异步获取图片资源)。

缺点:

  • 需要使用到私有 API browsingContextController 去注册 http/https。(其实现在大部分的离线包方案也是使用了这个私有 API 了)

方案二:Ajax Hook 全部 API

这个方案对应的是这里的 KKJSBridge/AjaxHook

原理介绍:此种方案,是使用 hook 的 XMLHttpRequest 对象来代理真实的 XMLHttpRequest 去发送请求,相当于是需要 hook ajax 中的所有方法,在 hook 的 open 方法里,调用 JSBridge 让 Native 去创建一个 NSMutableRequest 对象,然后在 hook 的 send 方法,把要发送的 body 通过 JSBridge 发送到 Native 侧,并把 body 设置给刚才创建的 NSMutableRequest 对象,并在 Native 侧完成请求后,通过 JS 执行函数,把请求结果通知给 JS 侧,JS 侧找到 hook 的 XMLHttpRequest 对象,最后调用 onreadystatechange 函数,让 H5 知道有请求结果了。

优点:

  • 没有使用私有 API。

缺点:

  • 需要 hook XMLHttpRequest 的所有方法。
  • 请求结果是通过 JSBrdige 来进行传输的,性能上肯定没有原生的性能好。
  • 不能支持 ajax 获取二进制的数据。要想支持的话,还需要额外的序列化工作。

更新历史

2020.11.24 (1.3.5)

  • 模块化管理 TS,方便代码阅读和维护
  • 移除 await 关键字,优化表单转 base64 的 js 代码

2020.11.21 (1.3.4)

  • 支持 JSBridge 同步调用
  • 支持 ajax 同步请求
  • 支持通过 document.cookie 同步从 NSHTTPCookieStorage 读取最新的 Cookie

2020.10.21 (1.2.1)

  • 正式版本,分别提供了 ajax hook 和 ajax urlprotocol hook 两种方案,可以根据具体需求自由选择。

2020.7.14 (1.1.5-beta9)

  • 可以根据需求选择是 ajax hook 还是 ajax urlprotocol hook

2020.6.23 (1.1.5-beta2)

  • 使用新的 hook 方式,提升 hook 兼容性
  • 支持 iframe 和 form 标签

2020.6.18 (1.1.0)

  • 支持 fetch hook

2020.5.18

  • 支持通过 off 方法取消事件监听

2020.3.3

  • 回收 webView 到复用池时,清除 sessionStorage
  • 支持 on 事件广播,让 H5 可以在多个地方注册事件监听

2019.10.23

  • 提供统一配置 configuration 方法,有些属性必须前置配置,否则会不生效

2019.10.22

  • 增加模块注册支持首次初始化

2019.9.29

  • 仅保留 Native 侧控制 ajax hook,主要是避免 ajax hook 时机不对时,会造成首次 hook 失败
  • 支持表单数据,支持图片上传
  • 支持 ajax 请求外部代理

参考

kkjsbridge's People

Contributors

karosli avatar tyrad avatar wjiuxing avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kkjsbridge's Issues

crashed on -[KKJSBridgeModuleXMLHttpRequestDispatcher generateXHR:objectId:responseCallback:]

报告一个闪退问题,辛苦了。
从 Bugly 数据显示,出现在系统 9.2.1、9.3.1、10.3.1 和 10.3.2 中。


#15 Thread SIGSEGV
SEGV_ACCERR

0 libobjc.A.dylib | objc_msgSend + 16
1 CoreFoundation | -[__NSCFString isEqual:] + 212
2 CoreFoundation | -[__NSDictionaryM setObject:forKey:] + 360
3 匿了 | -[KKJSBridgeModuleXMLHttpRequestDispatcher generateXHR:objectId:responseCallback:] (KKJSBridgeModuleXMLHttpRequestDispatcher.m:143)
4 匿了 | -[KKJSBridgeModuleXMLHttpRequestDispatcher open:params:responseCallback:] (KKJSBridgeModuleXMLHttpRequestDispatcher.m:0)
5 匿了 | __62-[KKJSBridgeMessageDispatcher dispatchCallbackMessageInQueue:]_block_invoke.90 (KKJSBridgeMessageDispatcher.m:154)
6 Foundation | ___NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 16
7 Foundation | -[NSBlockOperation main] + 96
8 Foundation | -[__NSOperationInternal _start:] + 612
9 Foundation | ___NSOQSchedule_f + 228
10 libdispatch.dylib | __dispatch_client_callout + 16
11 libdispatch.dylib | __dispatch_queue_serial_drain + 928
12 libdispatch.dylib | __dispatch_queue_invoke + 884
13 libdispatch.dylib | __dispatch_root_queue_drain + 540
14 libdispatch.dylib | __dispatch_worker_thread3 + 124
15 libsystem_pthread.dylib | _pthread_wqthread + 1096

使用cookie问题

我使用的时候是否是第一次设置了cookie,之后的请求就不需要管了?因为我目前就是这样操作了,但是后续也不行,我设置的cookie如下:
#pragma mark - 请求

  • (void)loadRequest {
    if (!self.url) {
    return;
    }

    NSString *cookie = @"JSESSIONID=2E17260F9C95A42F654C1BC6E6BD215B; Path=/oa; HttpOnly; SHAREJSESSIONID=61efdf3a-4c1b-4eba-90b5-551c4e295c95; Path=/oa; HttpOnly; rememberMe=deleteMe; Path=/oa; Max-Age=0; Expires=Wed; 04-Dec-2019 08:24:38 GMT";
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
    [request setValue:cookie forHTTPHeaderField:@"Cookie"];
    [self.webView loadRequest:request];
    }
    在这人页面上有附件下载,点击,感觉这个cookie设置没有效果

crashed on -[KKJSBridgeModuleRegister generateInstanceFromMetaClass:]

目前只遇见过一次,iPhone X, iOS 14.1(18A8395)

#13 Thread SIGABRT

0 libsystem_kernel.dylib | __pthread_kill + 8
1 libsystem_pthread.dylib | pthread_kill + 212
2 libsystem_c.dylib | abort + 100
3 libsystem_malloc.dylib | 0x000000019c5b4000 + 118832
4 libsystem_malloc.dylib | 0x000000019c5b4000 + 119272
5 libsystem_malloc.dylib | free + 432
6 CoreFoundation | 0x000000018db6b000 + 1541900
7 CoreFoundation | 0x000000018db6b000 + 43576
8 匿了 | -[KKJSBridgeModuleRegister generateInstanceFromMetaClass:] (KKJSBridgeModuleRegister.m:0)
9 匿了 | -[KKJSBridgeMessageDispatcher dispatchCallbackMessageInQueue:] (KKJSBridgeMessageDispatcher.m:0)
10 匿了 | __55-[KKJSBridgeMessageDispatcher dispatchCallbackMessage:]_block_invoke (KKJSBridgeMessageDispatcher.m:59)
11 Foundation | 0x000000018ee04000 + 1187016
12 Foundation | 0x000000018ee04000 + 125720
13 Foundation | 0x000000018ee04000 + 1195904
14 Foundation | 0x000000018ee04000 + 124884
15 Foundation | 0x000000018ee04000 + 1198456
16 Foundation | 0x000000018ee04000 + 1197124
17 libdispatch.dylib | 0x000000018d85d000 + 277688
18 libdispatch.dylib | 0x000000018d85d000 + 397952
19 libdispatch.dylib | 0x000000018d85d000 + 236548
20 libdispatch.dylib | 0x000000018d85d000 + 234348
21 libdispatch.dylib | 0x000000018d85d000 + 287832
22 libdispatch.dylib | 0x000000018d85d000 + 289784
23 libsystem_pthread.dylib | _pthread_wqthread + 212

Ajax hook 与 NSURLProtocol

感谢 @karosLi 的回复,为交流方便,把内容放到 github,谢谢。

我想用 Ajax hook,但不想有注册 protocol 的私有代码,这看起来有点难了。。。

  1. 首次(邮件)请教,内容如下:

你好,我是***。我公司的项目正在集成 KKJSBridge,很棒。
这几天发现项目中新加了 KKJSBridgeAjaxURLProtocol,没有想明白 和 Ajax 什么关系。
我在 KKJSBridge.js 中 看到 send 时通过 bridge.callNative 将请求给了 "ajax" module,像之前也没有用 Protocol。
在 cache body request 的 initialize 方法里注册了 URL Protocol。
想问下,这个 protocol 能做成可插拔的吗?我们之前也用 protocol,但是 WK 上要用私有接口才行,担心有审核风险没敢用…
祝好。

  1. @karosLi 回复:
  1. KKJSBridgeAjaxURLProtocol 是新的 hook 形式,是想要去提升 hook 的兼容性,因为 ajax hook 对于获取二进制的数据不太好处理,比如 ajax 获取一张图片,那就会获取失败。当然 ajax hook 对于纯文本的的请求是没有问题的。KKJSBridgeAjaxURLProtocol 目前还在测试阶段,后面的设想是替换掉目前的 ajax hook
  2. 关于 WK 上使用私有接口这点,没有审核风险。目前所有离线包都是需要注册 WK 的私有接口的。我们公司项目里也是注册私有接口去使用离线包的,既然已经使用了私有接口了,那再多一个 KKJSBridgeAjaxURLProtocol,影响也是不大的。
  3. 关于 protocol 热插拔,还是要看 WK 是否有注册,有注册 protocol 才会起作用,所以你想热插拔,可以控制是否开启 ajax hook 统一开关。
  4. 尽量还是在github上交流吧,邮件不一定来得及看。

大神请教一下,支持离线资源怎么做的

大神好,我最近在做wkwebview离线资源优化,我使用私有api进来url拦截,遇到post丢失body问题,于是找到您的神奇的KKJSBridge,发现post请求成功了,但是拦截不到URL了,想问下怎么做到支持离线资源

iOS 13.1.3 cookie不同步

copyNSHTTPCookieStorageToWKHTTPCookieStoreForWebViewOniOS11 在iOS 13.1.3及以上版本document.cookie获取不到Native设置的cookie。请问有谁遇到这个问题么?

小小建议

看了源码,楼主挺有心的。
有个小小的建议:
1.抽取核心通信功能
2.添加插件协议,可自定义插件
3.hook的等相关功能,作为无缝插件扩展

关于 cookie 获取失败的问题

再次请教大神个问题
webview初始化之前配置cookie,js是可以获取cookie的,但是当webview里加上这句,js就获取不到cookie了: _jsBridgeEngine = [KKJSBridgeEngine bridgeForWebView:self];

httponly的情况可以解决么

请问httponly的情况可以解决么?
项目页面与本地化通信有自己的一套通信方式自己写的,不是使用JSBridge会不会和项目的JSBridge冲突啊

ajax hook 之后,H5 图片上传获取不到进度信息,这个有好的方式解决吗

- (NSURLSessionDataTask *)normalDataTastWithRequest:(NSURLRequest *)request callbackDelegate:(NSObject<KKJSBridgeAjaxDelegate> *)callbackDelegate {
    return [[self.class ajaxSesstionManager] dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        // 处理响应数据
        [callbackDelegate JSBridgeAjax:callbackDelegate didReceiveResponse:response];
        if ([responseObject isKindOfClass:NSData.class]) {
            [callbackDelegate JSBridgeAjax:callbackDelegate didReceiveData:responseObject];
        } else if ([responseObject isKindOfClass:NSDictionary.class]) {
            NSData *responseData = [NSJSONSerialization dataWithJSONObject:responseObject options:0 error:nil];
            [callbackDelegate JSBridgeAjax:callbackDelegate didReceiveData:responseData];
        } else {
            NSData *responseData = [NSJSONSerialization dataWithJSONObject:@{} options:0 error:nil];
            [callbackDelegate JSBridgeAjax:callbackDelegate didReceiveData:responseData];
        }
        [callbackDelegate JSBridgeAjax:callbackDelegate didCompleteWithError:error];
    }];;
}

看代码里,没有实现进度相关的回调,NSURLProtocol 中也没有进度相关的方法

开启ajax拦截,但是回调函数里面获取不到结果

var ajaxUrl = "https://xxxx";
$.ajax({
url: ajaxUrl,
type: "POST",
success: function(data) {
console.log(data); // 此行不进来,获取不到回调数据
}
});

控制台拦截日志:
2019-12-27 11:37:43.145636+0800 KKJSBridgeDemo[26649:467666] KKJSBridge Receive ajax.open: {
"method" : "POST",
"scheme" : "http:",
"port" : "50000",
"id" : 993,
"async" : true,
"href" : "http://127.0.0.1:50000/index",
"host" : "127.0.0.1",
"referer" : null,
"useragent" : "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16A366 KKJSBridge/1.0.0",
"url" : "https://xxxx"
}
2019-12-27 11:37:43.146043+0800 KKJSBridgeDemo[26649:467676] KKJSBridge Receive ajax.setRequestHeader: {
"id" : 993,
"headerName" : "Accept",
"headerValue" : "/"
}
2019-12-27 11:37:43.146208+0800 KKJSBridgeDemo[26649:467893] KKJSBridge Receive ajax.send: {
"id" : 993
}

swift崩溃在了设置代理的方法

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[KKJSBridgeConfig setAjaxDelegateManager:]: unrecognized selector sent to class 0x1088d56f8'

ajax async:false同步请求

ajax async:false同步请求不生效,不会等success回调后再执行下面代码,

我尝试修复,使用window.prompt("SyncCall",obj),但是发现iOS14不行,iOS14以下正常了。

请问有什么好的方案解决同步问题吗?

关于拦截和私有api

如果采用KKJSBridge/AjaxProtocolHook以下2个问题想请教作者

  1. 私有api想要上架,有什么安全的方案推荐吗
  2. KKJSBridgeAjaxURLProtocol为什么要拦截所有的请求,而不是有满足本地离线包的时候再做处理

demo无效果

您好,运行demo,发现没有什么效果。也没看到相关日志打印,是需要修改什么地方吗?

补充一个兼容代码,看是否有帮助。

解决本地资源的ajax请求漏洞:
修改原因:
如果通过ajax请求发起本地资源的获取,比如url="abc.html"; href="file://test/index.html"
按照原来的代码,最终获取的地址为url="file://abc.html";
但实际应用获取到的url="file://test/abc.html"才对;

修改文件为:KKJSBridgeModuleXMLHttpRequestDispatcher.m
修改如下:

  • (void)open:(KKJSBridgeEngine *)engine params:(NSDictionary *)params responseCallback:(void (^)(NSDictionary *responseData))responseCallback {
    NSNumber *objectId = params[@"id"];
    KKJSBridgeXMLHttpRequest *xhr = [self getXHR:engine.webView objectId:objectId];
    if (!xhr) {
    xhr = [self generateXHR:engine objectId:objectId responseCallback:responseCallback];
    }
    NSString *method = params[@"method"];
    NSString *url = params[@"url"];
    NSString *userAgent = params[@"useragent"] ? params[@"useragent"] : @"iOS";
    NSString *referer = params[@"referer"];
    NSString *scheme = params[@"scheme"];
    NSString *host = params[@"host"];
    NSString *port = params[@"port"] ? params[@"port"] : @"";
    NSString *href = params[@"href"];

    NSURL *nativeURL = [NSURL URLWithString:url];
    if (!nativeURL.scheme) {

      /*------------兼容的代码 start------------*/
       // 说明是相对路径,一般是资源文件
       NSString* path = [href componentsSeparatedByString:@"."][0]; // 按“.”进行分割,获取第一个字符串 需要考虑类似地址:file://xxx/index.html#/testkey?abc=xxx
       path = [path stringByDeletingLastPathComponent]; // 删除最后一个“/”之后的内容
      
       if (![url hasPrefix:@"/"]){
           path = [NSString stringWithFormat:@"%@/",path];
       }
       // 获取当前目录,然后拼接
       url = [NSString stringWithFormat:@"%@%@",path,url];
       
       /*-----------兼容的代码 end-------------*/
    

// if (nativeURL.pathComponents > 0) {
// if (nativeURL.host) {
// url = [NSString stringWithFormat:@"%@%@",scheme, url];
// } else {
// if ([url hasPrefix:@"/"]) {// 处理 【/】情况
// NSString *tmpPath = url;
// NSString *tmpPort = port.length > 0 ? [NSString stringWithFormat:@":%@", port] : @"";
// url = [NSString stringWithFormat:@"%@//%@%@%@",scheme, host, tmpPort, tmpPath];
// } else { // 处理 【./】 【../】 【../../】和前面没有前缀的情况
// url = [[href stringByAppendingPathComponent:url] stringByStandardizingPath];
// }
// }
// } else {
// url = href;
// }
}

[xhr open:method url:url userAgent:userAgent referer:referer];

}

清除缓存问题,待验证

1.1.5-beta3,KKJSBridgeAjaxURLProtocol.m 117行代码

  • (void)clearRequestBody 方法

请求是异步的,有时候当前请求还没有结束,又有新的请求requestId又重新赋值,所以我觉得clearRequestBody会有问题

怎么兼容不使用KKWebView的情况

项目中接了三方广告商, 使用的是WKWebView,用browsingContextController的注册逻辑后导致他们正常的post请求丢失了body。

为了兼容直接使用WKWebView的情况, 把KKWebView逻辑拆成分类会不会更合理

KKJSBridgeAjaxURLProtocol.m 处理form表单的问题

KKJSBridgeAjaxURLProtocol.m 178行代码,处理form表单的问题
有些form提交不支持multipart/form-data,应该兼容更多form的enctype属性,如(application/x-www-form-urlencoded,multipart/form-data,text/plain),针对application/x-www-form-urlencoded还需要重新设置Content-Length

+canInitWithRequest: return No,body丢失问题

KKJSBridgeAjaxURLProtocol的 +canInitWithRequest:,当return NO的时候,恰好该请求为POST这类请求,body丢失,这中情况-startLoading方法不会执行的,取不到缓存body的,
针对这场景,我尝试在WKWebView的decidePolicyForNavigationAction:的方法里进行KKJSBridgeRegisterScheme 和 KKJSBridgeUnregisterScheme,WKWebView在iOS13.5.1不起作用,有兼容问题。
解决方案:把取缓存步骤提前到+canInitWithRequest:方法里,判断request为NSMutableURLRequest时,赋值body
或者您有其他更好的思路?

原生WKWebview如何使用KKJSBridge实现资源拦截

项目计划使用WKWebview,想拦截请求做离线加载。但拦截后遇到请求body丢失问题。
1、请教下原生WKWebview如何最小代价使用KKJSBridge实现拦截?
2、项目已使用Cordova和H5做通信,会和kkjsbridge冲突吗?
ps:
总的述求就是不使用kkjsbridge通信能力,也不关心cookie问题,仅仅做请求拦截并加载本地资源。

Response redirect 302

当服务返回302重定向后,WKWebView 并不能拦截到响应,所以此时下发的cookie是没办法干预的。
不知道这里是否有优化的空间?

派发事件时,发生 JavaScript 异常

我用的最新的 AjaxHook 代码(参照 #30 Ajax hook 与 NSURLProtocol)。

派发事件操作如下:

NSString *event = @"com.wjx.setting.xxx";
NSDictionary *userInfo = @{
    @"action": event,
    @"data": @YES
};
[_jsBridgeEngine dispatchEvent:event data:userInfo];

KKJSBridgeMessageDispatcher.m 202 行产生日志,为了看到更多信息,我修改了一下本地代码直接输出 error 替代 error.localizedDescription,

NSLog(@"KKJSBridge Error: evaluate JavaScript error %@, %@", error, messageJson);

日志如下:

KKJSBridge Error: evaluate JavaScript error Error Domain=WKErrorDomain Code=4 "发生 JavaScript 异常" UserInfo={WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=TypeError: undefined is not an object, WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=发生 JavaScript 异常}, {
data = {
action = "com.wjx.setting.xxx";
data = 0;
};
eventName = "com.wjx.setting.xxx";
messageType = event;
}

Form表单问题

页面中含有

标签这种情况,通过form.submit()或者form.onsubmit()提交表单,不会hook拦截

多次操作h5页面会Crash

两种hook方式都会这样,我没用你们的webView 用了自定义的

  • thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x3)
    • frame #0: 0x000000018a42f504 libobjc.A.dylibobjc_storeWeak + 200 frame #1: 0x0000000191e093b4 WebKit-[WKScriptMessage _initWithBody:webView:frameInfo:name:] + 132
      frame #2: 0x0000000191fd4474 WebKitScriptMessageHandlerDelegate::didPostMessage(WebKit::WebPageProxy&, WebKit::FrameInfoData const&, WebCore::SerializedScriptValue&) + 208 frame #3: 0x000000019216b328 WebKitWebKit::WebUserContentControllerProxy::didPostMessage(IPC::Connection&, WTF::ObjectIdentifierWebCore::PageIdentifierType, WebKit::FrameInfoData const&, unsigned long long, IPC::DataReference const&) + 180
      frame #4: 0x000000019236f40c WebKitvoid IPC::callMemberFunctionImpl<WebKit::WebUserContentControllerProxy, void (WebKit::WebUserContentControllerProxy::*)(IPC::Connection&, WTF::ObjectIdentifier<WebCore::PageIdentifierType>, WebKit::FrameInfoData const&, unsigned long long, IPC::DataReference const&), std::__1::tuple<WTF::ObjectIdentifier<WebCore::PageIdentifierType>, WebKit::FrameInfoData, unsigned long long, IPC::DataReference>, 0ul, 1ul, 2ul, 3ul>(WebKit::WebUserContentControllerProxy*, void (WebKit::WebUserContentControllerProxy::*)(IPC::Connection&, WTF::ObjectIdentifier<WebCore::PageIdentifierType>, WebKit::FrameInfoData const&, unsigned long long, IPC::DataReference const&), IPC::Connection&, std::__1::tuple<WTF::ObjectIdentifier<WebCore::PageIdentifierType>, WebKit::FrameInfoData, unsigned long long, IPC::DataReference>&&, std::__1::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul>) + 88 frame #5: 0x000000019236eac8 WebKitvoid IPC::handleMessage<Messages::WebUserContentControllerProxy::DidPostMessage, WebKit::WebUserContentControllerProxy, void (WebKit::WebUserContentControllerProxy::)(IPC::Connection&, WTF::ObjectIdentifierWebCore::PageIdentifierType, WebKit::FrameInfoData const&, unsigned long long, IPC::DataReference const&)>(IPC::Connection&, IPC::Decoder&, WebKit::WebUserContentControllerProxy, void (WebKit::WebUserContentControllerProxy::*)(IPC::Connection&, WTF::ObjectIdentifierWebCore::PageIdentifierType, WebKit::FrameInfoData const&, unsigned long long, IPC::DataReference const&)) + 104
      frame #6: 0x0000000191e3c880 WebKitIPC::MessageReceiverMap::dispatchMessage(IPC::Connection&, IPC::Decoder&) + 112 frame #7: 0x00000001920a8b68 WebKitWebKit::WebProcessProxy::didReceiveMessage(IPC::Connection&, IPC::Decoder&) + 36
      frame #8: 0x0000000191e29638 WebKitIPC::Connection::dispatchMessage(IPC::Decoder&) + 104 frame #9: 0x0000000191e25f88 WebKitIPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_deleteIPC::Decoder >) + 208
      frame #10: 0x0000000191e28d70 WebKitIPC::Connection::dispatchIncomingMessages() + 368 frame #11: 0x0000000199884914 JavaScriptCoreWTF::RunLoop::performWork() + 272
      frame #12: 0x0000000199884be4 JavaScriptCoreWTF::RunLoop::performWork(void*) + 40 frame #13: 0x000000018a678108 CoreFoundationCFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 28
      frame #14: 0x000000018a67805c CoreFoundation__CFRunLoopDoSource0 + 84 frame #15: 0x000000018a6777c8 CoreFoundation__CFRunLoopDoSources0 + 184
      frame #16: 0x000000018a672694 CoreFoundation__CFRunLoopRun + 1068 frame #17: 0x000000018a671f40 CoreFoundationCFRunLoopRunSpecific + 480
      frame #18: 0x0000000194902534 GraphicsServicesGSEventRunModal + 108 frame #19: 0x000000018e7fd580 UIKitCoreUIApplicationMain + 1940
      frame #20: 0x000000010470e0dcmain(argc=1, argv=0x000000016b727788) at main.m:14:16 frame #21: 0x000000018a4f0e18 libdyld.dylibstart + 4
      (lldb)

百度中搜索天猫后打不开

首先感谢作者做出了这么有用的demo,对我启发很大。
但在初步测试使用过程中,发现一个小BUG:
使用本demo,在百度中搜索"天猫“,显示结果后,结果中的天猫链接打不开(打开后白屏)
请问这个是什么原理?有没有相应的解决办法?

KKWebView重用导致历史记录异常

我描述一下我遇到的问题,我在项目中使用了重用逻辑
self.m_webView = [[KKWebViewPool sharedInstance] dequeueWebViewWithClass:KKWebView.class webViewHolder:self];
而在使用重用后初始化进入一个URL地址然后通过内部的链接跳转跳转到下一个地址,接着调用goBack(),此时无异常。
然后在回退到原生页面重新再打开WebView后,此时重用的WebView指针地址是完全一样的,说明是同一个WebView,然后再在index页面进入详情页面的地址时,此时调用goBack无效,查看该页面的backlist,发现它不存在历史的上一个页面。
而这个问题在不使用重用时而是直接new一个新的webview实例对象时不存在。

大神可以讲一下模块的作用和目的吗

最近在做离线缓存功能,看到您的库之后受益匪浅,但是研究您的代码的时候,发现有个模块的概念。一直没理解加模块是为了什么,以及场景。

假如我要封装一个通用的webview,所有的H5都使用,这时候模块对我来说还是否有意义

开启AjaxHook后,多次重复请求

开启AjaxHook后,protocol的 canInitWithRequest:(NSURLRequest *)request 多次请求同一个连接的内容,打印出来是GET方法,body为null

饿了吗的页面使用ajax后无法正常打开

你好:
采用ajax拦截打开 https://entu.ele.me 时候无法正常访问,页面提示错误;
如下图
image
我尝试进行修改:
image
访问后,依然报错,如下图:
image

但如果不使用ajax拦截是可以正常出现界面的,如下图:
image

烦请大佬帮忙排查排查,是什么原因导致的呢?

KKJSBridgeAjaxURLProtocol和MBHtmlURLProtocol不能同时注册吗

场景:初始化webView后同时注册KKJSBridgeAjaxURLProtocol和MBHtmlURLProtocol,并开启enableAjaxHook开关,实现ajaxDelegateManager外部代理。这个时候发现没有KKJSBridgeAjaxURLProtocol逻辑,抓包果然发现body丢失了。

想请问如果想实现离线包方案,应该怎么使用 KKJSBridgeAjaxURLProtocol和MBHtmlURLProtocol

请求参数拼接 2 次导致连接不合法问题

H5 碰到发起 GET 请求时,请求参数重复添加 2 次,导致连接错误页面无法展示问题

image
如上图中,driverNo beginTime endTime? 都添加了 2 遍,排查后原因是 H5 发起请求时连接中直接拼接了参数,如果不直接拼接,在添加参数的方法中添加则不会有问题,这个能否优化

KKJSBridge 在 iOS 9 设备上未能初始化

项目中需要适配 iOS 9的设备,但是不能工作。

我用自带的 demo 发现 KKJSBridge 并未初始化。

在 module 测试页面,打开 eruda 的 Console,点击其中一个按钮,发现报错如下:

TypeError: undefined is not an object(evaluating 'window.KKJSBridge.call')

页面中iframe内页调用native方法之后,回调函数不执行

问题描述:

  1. 外层页面上通过iframe嵌入内层页面,内层页面引入桥层脚本,并通过内层页面调用native方法,并传入回调;
  2. native执行函数之后,调用回调时,无法触发回调函数。

问题分析:

由于执行回调函数时,是通过webview当前上下文执行脚本触发的,但是iframe内页的上下文和外层页面的上下文是隔离的,可能也不同域,因此在外层页面上无法执行内层页面的脚本。而且如果有多个iframe内页的话,也不好标记该调用是哪个iframe发起的

如上,求一个可靠的解决方案。

备注:目前有一些多业务模块融合的情况,不同业务模块之间可能有相互引用的情况,而引用方式为了尽量减少对被接入方的改动需求,因此都采用iframe嵌入对方页面的方案。

Remove UIWebView

苹果审核不允许出现 UIWebView,请移除,谢谢。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.