├── AppHost ├── logo_small.png ├── RemoteDebug │ ├── logo.png │ ├── favicon.ico │ ├── images │ │ ├── mobile to pc.png │ │ └── pc to mobile.png │ ├── thirdParty │ │ └── weinreSupport.js │ ├── AHDebugServerManager.h │ ├── renderjson.css │ ├── AHDebugViewController.h │ ├── profile │ │ ├── pageTiming_for_mac.js │ │ └── pageTiming.js │ ├── components │ │ └── tool-panel.js │ ├── testcase.tmpl │ └── server.css ├── resources │ ├── WechatIMG19.jpeg │ ├── WechatIMG20.jpeg │ └── pushstate_test.html ├── Core │ ├── URLChecker │ │ ├── app-access.txt │ │ ├── AHAppWhiteListParser.h │ │ ├── AHURLChecker.h │ │ ├── AHAppWhiteListParser.m │ │ └── AHURLChecker.m │ ├── Response │ │ ├── AHBuiltInResponse.h │ │ ├── AHAppLoggerResponse.h │ │ ├── AHNavigationResponse.h │ │ ├── AHNavigationBarResponse.h │ │ ├── AHPrefetchResponse.h │ │ ├── AHDebugResponse.h │ │ ├── AHAppLoggerResponse.m │ │ ├── AHResponseManager.h │ │ ├── AHPrefetchResponse.m │ │ ├── AHBuiltInResponse.m │ │ ├── AHNavigationBarResponse.m │ │ ├── AHNavigationResponse.m │ │ └── AHResponseManager.m │ ├── AppHostViewController+Extend.m │ ├── AHJSCoreManager.h │ ├── Helper │ │ ├── AHUtil.h │ │ ├── AHScriptMessageDelegate.h │ │ ├── AppHostCookie.h │ │ ├── AHWebViewScrollPositionManager.h │ │ ├── AHScriptMessageDelegate.m │ │ ├── AHUtil.m │ │ ├── AHWebViewScrollPositionManager.m │ │ └── AppHostCookie.m │ ├── AHHTTPSchemeTaskDelegate.h │ ├── AHJSCoreManager.m │ ├── AppHostViewController+Progressor.h │ ├── AppHostViewController+Dispatch.h │ ├── AHSchemeTaskDelegate.h │ ├── AppHostResponse.h │ ├── AppHostViewController+Utils.h │ ├── Intermediate │ │ └── AHRequestMediate.h │ ├── AppHostViewController+Timing.h │ ├── html │ │ ├── appHost_version_1.5.0.js │ │ └── eval.js │ ├── AppHostViewController+Timing.m │ ├── AHHTTPSchemeTaskDelegate.m │ ├── AppHostViewController+Scripts.h │ ├── AppHostViewController+Extend.h │ ├── AppHostViewController+Dispatch.m │ ├── AppHostProtocol.h │ ├── AppHostViewController.h │ ├── AppHostViewController+Progressor.m │ ├── AHSchemeTaskDelegate.m │ ├── AppHostEnum.h │ ├── AppHostResponse.m │ └── AppHostViewController+Utils.m ├── NetworkCapture │ ├── WKWebView+HandleURLScheme.h │ └── WKWebView+HandleURLScheme.m ├── Boost │ ├── Prefetch │ │ ├── AHLogProviderProtocol.h │ │ ├── AHPrefetchLoaderProtocol.h │ │ ├── AHPrefechLoader.h │ │ ├── AHPrefetchMonitor.h │ │ └── AHPrefetchMonitor.m │ └── Preloader │ │ ├── AHSimpleWebViewController.h │ │ ├── AHSimpleWebViewController.m │ │ ├── AHWebViewPreLoader.h │ │ ├── AHWebViewPreLoader.m │ │ └── preload_resources.html ├── Info.plist ├── ThirdParty │ ├── Objective-C-HMTL-Parser │ │ ├── HTMLParser.h │ │ ├── README.md │ │ ├── HTMLParser.m │ │ └── HTMLNode.h │ ├── GCDWebServer │ │ ├── Requests │ │ │ ├── GCDWebServerFileRequest.h │ │ │ ├── GCDWebServerURLEncodedFormRequest.h │ │ │ ├── GCDWebServerURLEncodedFormRequest.m │ │ │ ├── GCDWebServerDataRequest.h │ │ │ ├── GCDWebServerDataRequest.m │ │ │ ├── GCDWebServerFileRequest.m │ │ │ └── GCDWebServerMultiPartFormRequest.h │ │ ├── Responses │ │ │ ├── GCDWebServerStreamedResponse.m │ │ │ ├── GCDWebServerStreamedResponse.h │ │ │ ├── GCDWebServerErrorResponse.h │ │ │ ├── GCDWebServerDataResponse.h │ │ │ ├── GCDWebServerFileResponse.h │ │ │ └── GCDWebServerDataResponse.m │ │ └── Core │ │ │ ├── GCDWebServerFunctions.h │ │ │ └── GCDWebServerHTTPStatusCodes.h │ └── Reachability │ │ └── Reachability.h └── AppHost.h ├── AppHost.xcodeproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── .gitignore ├── feature.md └── README.md /AppHost/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hite/AppHost/HEAD/AppHost/logo_small.png -------------------------------------------------------------------------------- /AppHost/RemoteDebug/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hite/AppHost/HEAD/AppHost/RemoteDebug/logo.png -------------------------------------------------------------------------------- /AppHost/RemoteDebug/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hite/AppHost/HEAD/AppHost/RemoteDebug/favicon.ico -------------------------------------------------------------------------------- /AppHost/resources/WechatIMG19.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hite/AppHost/HEAD/AppHost/resources/WechatIMG19.jpeg -------------------------------------------------------------------------------- /AppHost/resources/WechatIMG20.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hite/AppHost/HEAD/AppHost/resources/WechatIMG20.jpeg -------------------------------------------------------------------------------- /AppHost/RemoteDebug/images/mobile to pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hite/AppHost/HEAD/AppHost/RemoteDebug/images/mobile to pc.png -------------------------------------------------------------------------------- /AppHost/RemoteDebug/images/pc to mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hite/AppHost/HEAD/AppHost/RemoteDebug/images/pc to mobile.png -------------------------------------------------------------------------------- /AppHost/Core/URLChecker/app-access.txt: -------------------------------------------------------------------------------- 1 | #协议和h5接口权限开放白名单 2 | schema-open-url: 3 | you.163.com 4 | *.mail.163.com 5 | 6 | 7 | apphost: 8 | you.163.com 9 | *.mail.163.com 10 | 11 | -------------------------------------------------------------------------------- /AppHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AppHost/RemoteDebug/thirdParty/weinreSupport.js: -------------------------------------------------------------------------------- 1 | if (window.appHost) { 2 | window.appHost.on('weinre.enable', function () { 3 | // 加载完成的页面要使用远程调试需要重新加载 webview 才行 4 | window.location.reload(); 5 | }); 6 | } else { 7 | console.log('无 AppHost 对象'); 8 | } 9 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHBuiltInResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKBuiltInResponse.h 3 | 4 | // 5 | // Created by liang on 06/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AppHostResponse.h" 10 | 11 | @interface AHBuiltInResponse : AppHostResponse 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHAppLoggerResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKAppLoggerResponse.h 3 | 4 | // 5 | // Created by liang on 06/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AppHostResponse.h" 10 | 11 | @interface AHAppLoggerResponse : AppHostResponse 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHNavigationResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKNavigationResponse.h 3 | 4 | // 5 | // Created by liang on 05/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AppHostResponse.h" 10 | 11 | @interface AHNavigationResponse : AppHostResponse 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHNavigationBarResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKNavigationBarResponse.h 3 | 4 | // 5 | // Created by liang on 05/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AppHostResponse.h" 10 | 11 | @interface AHNavigationBarResponse : AppHostResponse 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AppHost.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Extend.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Extend.m 3 | // AppHost 4 | // 5 | // Created by liang on 2019/4/16. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AppHostViewController+Extend.h" 10 | 11 | @implementation AppHostViewController (Extend) 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AppHost/NetworkCapture/WKWebView+HandleURLScheme.h: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+HandleURLScheme.h 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/11. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface WKWebView (HandleURLScheme) 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /AppHost/Core/AHJSCoreManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHJSCoreManager.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/4/11. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AHJSCoreManager : NSObject 14 | 15 | +(instancetype)defaultManager; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHPrefetchResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHPrefetchResponse.h 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/12. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppHostResponse.h" 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AHPrefetchResponse : AppHostResponse 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /AppHost/Core/Helper/AHUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHUtil.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/22. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AHUtil : NSObject 14 | 15 | + (BOOL)isNetworkUrl:(NSString *)url; 16 | 17 | + (NSString *)traceId; 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /AppHost/Core/AHHTTPSchemeTaskDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHHTTPSchemeTaskDelegate.h 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/11. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppHostEnum.h" 11 | 12 | @import WebKit; 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface AHHTTPSchemeTaskDelegate : NSObject 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /AppHost/Core/Helper/AHScriptMessageDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKScriptMessageDelegate.h 3 | 4 | // 5 | // Created by liang on 16/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @import WebKit; 12 | 13 | @interface AHScriptMessageDelegate : NSObject 14 | 15 | - (instancetype)initWithDelegate:(id)scriptDelegate; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AppHost/NetworkCapture/WKWebView+HandleURLScheme.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+HandleURLScheme.m 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/11. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import "WKWebView+HandleURLScheme.h" 10 | 11 | @implementation WKWebView (HandleURLScheme) 12 | 13 | + (BOOL)handlesURLScheme:(NSString *)urlScheme{ 14 | NSLog(@"Scheme Check for url = %@", urlScheme); 15 | return NO; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /AppHost/Boost/Prefetch/AHLogProviderProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHLogProvider.h 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/12. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @protocol AHLogProviderProtocol 14 | 15 | - (void)logAction:(NSString *)actionName tags:(NSDictionary *)tags fields:(NSDictionary *)fields; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /AppHost/RemoteDebug/AHDebugServerManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHDebugServerManager.h 3 | // AppHost 4 | // 5 | // Created by liang on 2018/12/29. 6 | // Copyright © 2018 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | @interface AHDebugServerManager : NSObject 13 | 14 | + (instancetype)sharedInstance; 15 | 16 | - (void)start; 17 | 18 | - (void)stop; 19 | 20 | - (void)showDebugWindow; 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHDebugResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHDebugResponse.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/1/22. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppHostResponse.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | static NSString *kAppHostTestCaseFileName = @"testcase.html"; 15 | @interface AHDebugResponse : AppHostResponse 16 | 17 | + (void)setupDebugger; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /AppHost/Boost/Preloader/AHSimpleWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHSimpleWebViewController.h 3 | // 4 | // 5 | // Created by liang on 2019/7/17. 6 | // Copyright © 2019 Smily.Co. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AHSimpleWebViewController : UIViewController 14 | 15 | @property (nonatomic, strong) NSString *htmlString; 16 | 17 | @property (nonatomic, strong) NSString *domain; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /AppHost/Boost/Prefetch/AHPrefetchLoaderProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHPrefetchLoaderProtocal.h 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/12. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | 12 | @protocol AHPrefetchLoaderProtocol 13 | 14 | + (instancetype)sharedInstance; 15 | 16 | - (void)prepareDataForUrl:(NSString *)url; 17 | 18 | - (void)clearCacheDataForUrl:(NSString *)url; 19 | 20 | - (NSDictionary *)cacheForHash:(int32_t)hash; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /AppHost/Core/URLChecker/AHAppWhiteListParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKAppWhiteListParser.h 3 | 4 | // 5 | // Created by hite on 4/21/16. 6 | // Copyright © 2016 smilly.co All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AHAppWhiteListParser : NSObject 12 | + (instancetype)sharedManager; 13 | /** 14 | * 读取一个文件,解析为一个规则的对象,返回 15 | * 16 | * @param fileContent 文件内容 17 | * @return 包括key,value的对象,value是域名字符串的数组。 18 | */ 19 | - (NSDictionary *)parserFileContent:(NSString *)fileContent; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /AppHost/Core/AHJSCoreManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // AHJSCoreManager.m 3 | // AppHost 4 | // 5 | // Created by liang on 2019/4/11. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AHJSCoreManager.h" 10 | #import "AHUtil.h" 11 | 12 | @implementation AHJSCoreManager 13 | 14 | +(instancetype)defaultManager{ 15 | static AHJSCoreManager *_instance = nil; 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | _instance = [AHJSCoreManager new]; 19 | }); 20 | 21 | return _instance; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /AppHost/Core/Helper/AppHostCookie.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKAppHostCookie.h 3 | 4 | // 5 | // Created by liang on 06/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @import WebKit; 12 | 13 | @interface AppHostCookie : NSObject 14 | /** 15 | 针对处理cookie发生变化时的调用。如登录成功后的页面内跳转 16 | */ 17 | + (NSMutableArray *)cookieJavaScriptArray; 18 | 19 | + (WKProcessPool *)sharedPoolManager; 20 | 21 | // 以下和 cookie 同步相关 22 | + (void)setLoginCookieHasBeenSynced:(BOOL)synced; 23 | 24 | + (BOOL)loginCookieHasBeenSynced; 25 | @end 26 | -------------------------------------------------------------------------------- /AppHost/RemoteDebug/renderjson.css: -------------------------------------------------------------------------------- 1 | .renderjson a { text-decoration: none; } 2 | .renderjson .disclosure { color: #555; 3 | font-size: 150%; } 4 | .renderjson .syntax { color: grey; } 5 | .renderjson .string { color: #af0404; } 6 | .renderjson .number { color: #af0404; } 7 | .renderjson .boolean { color: #af0404; } 8 | .renderjson .key { color: #333; } 9 | .renderjson .keyword { color: lightgoldenrodyellow; } 10 | .renderjson .object.syntax { color: lightseagreen; } 11 | .renderjson .array.syntax { color: #af0404; } -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Progressor.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Progressor.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/23. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppHostViewController (Progressor) 12 | 13 | @property (nonatomic, strong) NSTimer *clearProgressorTimer; 14 | 15 | @property (nonatomic, strong) UIProgressView *progressorView; 16 | 17 | - (void)startProgressor; 18 | 19 | - (void)stopProgressor; 20 | #pragma mark - lifecycle 21 | - (void)setupProgressor; 22 | - (void)teardownProgressor; 23 | 24 | @end 25 | 26 | -------------------------------------------------------------------------------- /AppHost/Core/Helper/AHWebViewScrollPositionManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKWebViewScrollPositionManager.h 3 | 4 | // 5 | // Created by liang on 02/05/2017. 6 | // Copyright © 2017 smilly.co All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface AHWebViewScrollPositionManager : NSObject 13 | 14 | + (instancetype)sharedInstance; 15 | 16 | - (void)cacheURL:(NSURL *)url position:(CGFloat)lastPosition; 17 | 18 | - (CGFloat)positionForCacheURL:(NSURL *)url; 19 | 20 | - (void)emptyURLCache:(NSURL *)url; 21 | 22 | /** 23 | 清除所有对象 24 | */ 25 | - (void)clearAllCache; 26 | @end 27 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Dispatch.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Dispatch.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/23. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AppHostViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AppHostViewController (Dispatch) 14 | 15 | /** 16 | * 核心的h5调用native接口的分发器; 17 | * @return 是否已经被处理,YES 表示可被处理; 18 | */ 19 | - (BOOL)callNative:(NSString *)action parameter:(NSDictionary *)paramDict; 20 | 21 | #pragma mark - like private 22 | 23 | - (void)dispatchParsingParameter:(NSDictionary *)contentJSON; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /AppHost/Core/AHSchemeTaskDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHSchemeTaskResponse.h 3 | // AppHost 4 | // 5 | // Created by liang on 2018/12/29. 6 | // Copyright © 2018 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppHostEnum.h" 11 | 12 | @import WebKit; 13 | 14 | typedef NSData*_Nonnull(^bSchemeTaskHandler)(WKWebView *_Nonnull, id _Nonnull, NSString *_Nullable * _Nullable mime); 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | @interface AHSchemeTaskDelegate : NSObject 19 | 20 | /** 21 | 添加自定义的处理逻辑 22 | */ 23 | - (void)addHandler:(bSchemeTaskHandler)handler forDomain:(NSString */* js */)domain; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKAppHostResponse.h 3 | 4 | // 5 | // Created by liang on 05/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppHostProtocol.h" 11 | #import "AppHostEnum.h" 12 | 13 | @interface AppHostResponse : NSObject 14 | 15 | /** 16 | * 调用 callback 的函数,这个函数是 js 端调用方法时,注册在 js 端的 block。 17 | * 这里传入的第一个参数是 和这个 js 端 block 相关联的 key。js 根据这个 key 找到这个 block 并且执行 18 | */ 19 | - (void)fireCallback:(NSString *)callbackKey param:(NSDictionary *)paramDict; 20 | 21 | /** 22 | * 辅助方法,转发到 appHost 的接口 23 | */ 24 | - (void)fire:(NSString *)actionName param:(NSDictionary *)paramDict; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /AppHost/Core/URLChecker/AHURLChecker.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKURLChecker.h 3 | // 4 | // Created by hite on 4/22/16. 5 | // Copyright © 2016 smilly.co. All rights reserved. 6 | // 7 | 8 | #import 9 | /** 10 | * 定义授权的类型 11 | */ 12 | typedef NS_ENUM(NSUInteger, AHAuthorizationType) { 13 | 14 | AHAuthorizationTypeSchema, 15 | /** 16 | * 是否容许调用apphost接口 17 | */ 18 | AHAuthorizationTypeAppHost 19 | }; 20 | 21 | @interface AHURLChecker : NSObject 22 | 23 | + (instancetype)sharedManager; 24 | 25 | /** 26 | * 检查是否容许url访问authype的接口 27 | * 28 | * @param url NSURL对象 29 | * @param authType 授权类型的枚举 30 | * 31 | * @return 是否容许,yes表示容许 32 | */ 33 | - (BOOL)checkURL:(NSURL *)url forAuthorizationType:(AHAuthorizationType)authType; 34 | 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHAppLoggerResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKAppLoggerResponse.m 3 | 4 | // 5 | // Created by liang on 06/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AHAppLoggerResponse.h" 10 | 11 | @implementation AHAppLoggerResponse 12 | 13 | + (NSDictionary *)supportActionList 14 | { 15 | return @{ 16 | @"log_" : @"1" 17 | }; 18 | } 19 | 20 | ah_doc_begin(log_, "在 xcode 控制台输出日志") 21 | ah_doc_param(logData, "日志字段,通常是json 对象") 22 | ah_doc_code(window.appHost.invoke("log",{"text":"Error"})) 23 | ah_doc_code_expect("会在 xcode 控制台输出日志信息,输出 text: Error, 日志包含了 [AppHost] 前缀") 24 | ah_doc_end 25 | - (void)log:(NSDictionary *)logData 26 | { 27 | AHLog(@"Logs from webview: %@", logData); 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Utils.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/23. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AppHostViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AppHostViewController (Utils) 14 | 15 | - (NSDictionary *)supportListByNow; 16 | 17 | - (void)showTextTip:(NSString *)text; 18 | 19 | - (void)showTextTip:(NSString *)text hideAfterDelay:(CGFloat)delay; 20 | 21 | - (void)dealWithViewHistory; 22 | 23 | - (void)popOutImmediately; 24 | 25 | - (BOOL)isExternalSchemeRequest:(NSString *)url; 26 | 27 | - (BOOL)isItmsAppsRequest:(NSString *)url; 28 | 29 | - (void)logRequestAndResponse:(NSString *)str type:(NSString *)type; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /AppHost/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /AppHost/Core/Intermediate/AHRequestMediate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHRequestMediate.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/22. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AHRequestMediate : NSObject 14 | 15 | /** 16 | 处理 script 和 style。图片由 WKSchemeTaskHandler 处理 17 | 18 | @param fileName 文件夹里的入口文件,通常是 index.html 19 | @param directory 包含静态资源的文件夹,可以包含图片、js、css 文件。字体文件等不支持 20 | @param domain 资源的相对地址,组装绝对地址时的前缀,表示此页面的加载 url。 21 | @param output 将所有资源都内置到 html 的文件。 22 | @return 是否内侧处理时,发生过错误。非 0 表示出错过,但不代码 output 是无效的。 23 | */ 24 | + (int)interMediateFile:(NSString *)fileName inDirectory:(NSURL *)directory domain:(NSString *)domain output:(NSString *_Nonnull*_Nonnull)output; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/Objective-C-HMTL-Parser/HTMLParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // HTMLParser.h 3 | // StackOverflow 4 | // 5 | // Created by Ben Reeves on 09/03/2010. 6 | // Copyright 2010 Ben Reeves. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "HTMLNode.h" 12 | 13 | @class HTMLNode; 14 | 15 | @interface HTMLParser : NSObject 16 | { 17 | @public 18 | htmlDocPtr _doc; 19 | } 20 | 21 | -(id)initWithContentsOfURL:(NSURL*)url error:(NSError**)error; 22 | -(id)initWithData:(NSData*)data error:(NSError**)error; 23 | -(id)initWithString:(NSString*)string error:(NSError**)error; 24 | 25 | //Returns the doc tag 26 | -(HTMLNode*)doc; 27 | 28 | //Returns the body tag 29 | -(HTMLNode*)body; 30 | 31 | //Returns the html tag 32 | -(HTMLNode*)html; 33 | 34 | //Returns the head tag 35 | - (HTMLNode*)head; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /AppHost/RemoteDebug/AHDebugViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHDebugWindow.h 3 | // AppHost 4 | // 5 | // Created by admin on 14/1/2019. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @class AHDebugViewController; 15 | 16 | @protocol AHDebugViewDelegate 17 | 18 | - (void)onCloseWindow:(AHDebugViewController *)viewController; 19 | 20 | - (void)fetchData:(AHDebugViewController *)viewController completion:(void (^)(NSArray *))completion; 21 | 22 | @end 23 | 24 | @interface AHDebugViewController : UIViewController 25 | 26 | @property (nonatomic, weak) id debugViewDelegate; 27 | 28 | - (void)showNewLine:(NSArray *)line; 29 | 30 | - (void)onWindowHide; 31 | - (void)onWindowShow; 32 | 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /AppHost/Boost/Prefetch/AHPrefechLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHPrefechLoader.h 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/12. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AHPrefetchLoaderProtocol.h" 11 | #import "AHLogProviderProtocol.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface AHPrefetchfigInterfacesModel : NSObject 16 | 17 | @property (nonatomic, strong) NSString *api; 18 | 19 | @property (nonatomic, strong) NSString *method; 20 | 21 | @end 22 | 23 | @interface AHPrefetchfigItemModel : NSObject 24 | 25 | @property (nonatomic, strong) NSString *url; 26 | 27 | @property (nonatomic, strong) NSArray *interfaces; 28 | 29 | @end 30 | 31 | 32 | @interface AHPrefechLoader : NSObject 33 | 34 | @property (nonatomic, strong) NSString *prefetchUrl; 35 | 36 | - (void)setLogger:(id)logger; 37 | 38 | @end 39 | 40 | NS_ASSUME_NONNULL_END 41 | -------------------------------------------------------------------------------- /AppHost/Core/Helper/AHScriptMessageDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKScriptMessageDelegate.m 3 | 4 | // 5 | // Created by liang on 16/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AHScriptMessageDelegate.h" 10 | 11 | @interface AHScriptMessageDelegate() 12 | 13 | @property (nonatomic, weak) id scriptDelegate; 14 | 15 | @end 16 | 17 | @implementation AHScriptMessageDelegate 18 | 19 | - (instancetype)initWithDelegate:(id)scriptDelegate 20 | { 21 | self = [super init]; 22 | if (self) { 23 | _scriptDelegate = scriptDelegate; 24 | } 25 | return self; 26 | } 27 | 28 | - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message 29 | { 30 | [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message]; 31 | } 32 | 33 | - (void)dealloc 34 | { 35 | NSLog(@"MKScriptMessageDelegate dealloc"); 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /AppHost/AppHost.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppHost.h 3 | // AppHost 4 | // 5 | // Created by liang on 2018/12/27. 6 | // Copyright © 2018 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for AppHost. 12 | FOUNDATION_EXPORT double AppHostVersionNumber; 13 | 14 | //! Project version string for AppHost. 15 | FOUNDATION_EXPORT const unsigned char AppHostVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | #import 30 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Timing.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Timing.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/4/2. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | static NSString *kAppHostTimingLoadRequest = @"loadRequest"; 14 | static NSString *kAppHostTimingWebViewInit = @"webViewInit"; 15 | static NSString *kAppHostTimingDidFinishNavigation = @"didFinishNavigation"; 16 | static NSString *kAppHostTimingDecidePolicyForNavigationAction = @"decidePolicyForNavigationAction"; 17 | static NSString *kAppHostTimingAddUserScript = @"addUserScript"; 18 | @interface AppHostViewController (Timing) 19 | 20 | /** 21 | 保存所有 mark 的起点数据。 22 | */ 23 | @property (nonatomic, strong) NSMutableDictionary *marks; 24 | 25 | /** 26 | 记录起点 27 | 28 | @param markName 起点的别名,用来和后面 mearsue 配合使用 29 | */ 30 | - (void)mark:(NSString *)markName; 31 | 32 | /** 33 | 计算从此时到 markName 别打标记时的时间耗时 34 | 35 | */ 36 | - (void)measure:(NSString *)endMarkName to:(NSString *)startMark; 37 | 38 | @end 39 | 40 | NS_ASSUME_NONNULL_END 41 | -------------------------------------------------------------------------------- /AppHost/RemoteDebug/profile/pageTiming_for_mac.js: -------------------------------------------------------------------------------- 1 | function ah_timing(_timing) { 2 | window.mobile_performance_timing = _timing; 3 | 4 | if (!window.__profiler || window.__profiler.scriptLoaded !== true) { 5 | var d = document, h = d.getElementsByTagName('head')[0], l = d.createElement('div'), c = function () { 6 | if (l) { 7 | d.body.removeChild(l); 8 | } 9 | window.__profiler = window.__profiler || new __Profiler(); 10 | window.__profiler.init(); 11 | __profiler.scriptLoaded = true; 12 | }, t = new Date(); 13 | l.style.cssText = 'z-index:999;position:fixed;top:10px;left:10px;display:inline;width:auto;font-size:14px;line-height:1.5em;font-family:Helvetica,Calibri,Arial,sans-serif;text-shadow:none;padding:3px 10px 0;background:#FFFDF2;box-shadow:0 0 0 3px rgba(0,0,0,.25),0 0 5px 5px rgba(0,0,0,.25); border-radius:1px'; 14 | l.innerHTML = 'Just a moment'; 15 | d.body.appendChild(l); 16 | c(); 17 | } 18 | else if (window.__profiler instanceof __Profiler) { 19 | window.__profiler.init(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 liang wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHResponseManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHResponseManager.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/1/22. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppHostResponse.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface AHResponseManager : NSObject 15 | 16 | /** 17 | 自定义response类 18 | */ 19 | @property (nonatomic, strong, readonly) NSMutableArray *customResponseClasses; 20 | 21 | + (instancetype)defaultManager; 22 | 23 | #ifdef AH_DEBUG 24 | 25 | /** 26 | 获取所有注册的 Response 的接口 27 | 28 | @return 返回所有 class 支持的 methods,以 class 为 key。key 对应的数据包含所有这个 class 支持的方法 29 | */ 30 | - (NSDictionary *)allResponseMethods; 31 | 32 | #endif 33 | #pragma mark - 自定义 Response 区域 34 | /** 35 | 注册自定义的 Response 36 | 37 | @param cls 可以处理响应的子类 class,其符合 AppHostProtocol 38 | */ 39 | - (void)addCustomResponse:(Class)cls; 40 | 41 | - (id)responseForActionSignature:(NSString *)action withAppHost:(AppHostViewController * _Nonnull)appHost; 42 | 43 | - (Class)responseForActionSignature:(NSString *)signature; 44 | 45 | - (NSString *)actionSignature:(NSString *)action withParam:(BOOL)hasParamDict withCallback:(BOOL)hasCallback; 46 | @end 47 | 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /AppHost/Boost/Preloader/AHSimpleWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AHSimpleWebViewController.m 3 | // 4 | // 5 | // Created by liang on 2019/7/17. 6 | // Copyright © 2019 Smily.Co. All rights reserved. 7 | // 8 | 9 | #import "AHSimpleWebViewController.h" 10 | @import WebKit; 11 | 12 | @interface AHSimpleWebViewController () 13 | @property (nonatomic, strong) WKWebView *webView; 14 | @end 15 | 16 | @implementation AHSimpleWebViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | // Do any additional setup after loading the view. 21 | WKWebView *webView = [WKWebView new]; 22 | self.webView = webView; 23 | webView.frame = self.view.bounds; 24 | [self.view addSubview:webView]; 25 | 26 | if (self.htmlString.length > 0) { 27 | [webView loadHTMLString:self.htmlString baseURL:[NSURL URLWithString:self.domain?:@"https://m.you.163.com"] ]; 28 | } 29 | } 30 | 31 | /* 32 | #pragma mark - Navigation 33 | 34 | // In a storyboard-based application, you will often want to do a little preparation before navigation 35 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 36 | // Get the new view controller using [segue destinationViewController]. 37 | // Pass the selected object to the new view controller. 38 | } 39 | */ 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /AppHost/Core/Helper/AHUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // AHUtil.m 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/22. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AHUtil.h" 10 | #import 11 | 12 | @implementation AHUtil 13 | 14 | static char *base36enc(long long unsigned int value) 15 | { 16 | char base36[37] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 17 | /* log(2**64) / log(36) = 12.38 => max 13 char + '\0' */ 18 | char buffer[14]; 19 | unsigned int offset = sizeof(buffer); 20 | 21 | buffer[--offset] = '\0'; 22 | do { 23 | buffer[--offset] = base36[value % 36]; 24 | } while (value /= 36); 25 | 26 | return strdup(&buffer[offset]); 27 | } 28 | 29 | + (NSString *)traceId 30 | { 31 | long long hash = [[[[NSString stringWithFormat:@"%f", CFAbsoluteTimeGetCurrent()] stringByReplacingOccurrencesOfString:@"." withString:@""] substringFromIndex:4] longLongValue]; 32 | 33 | char *str = base36enc(hash); 34 | 35 | NSString *traceId = [NSString stringWithFormat:@"i%@", [NSString stringWithUTF8String:str]]; 36 | 37 | return traceId; 38 | } 39 | 40 | + (BOOL)isNetworkUrl:(NSString *)url 41 | { 42 | return [url hasPrefix:@"http://"] || [url hasPrefix:@"https://"] || [url hasPrefix:@"//"]; 43 | } 44 | @end 45 | -------------------------------------------------------------------------------- /AppHost/Core/html/appHost_version_1.5.0.js: -------------------------------------------------------------------------------- 1 | !function() { 2 | window.appHost = { 3 | version: "1.5.1" 4 | }; 5 | 6 | var callbackPool = {}; 7 | var ack_no = 1; 8 | window.appHost.invoke = function(_action, _data, _callback) { 9 | var rndKey = 'cbk_' + new Date().getTime(); 10 | var fullParam = { 11 | action: _action, 12 | param: _data 13 | }; 14 | if (_callback) { //如果有回调函数。 15 | var rndKey = 'cbk_' + ack_no++; 16 | fullParam.callbackKey = rndKey; 17 | callbackPool[rndKey] = _callback; 18 | } 19 | 20 | window.webkit.messageHandlers.kAHScriptHandlerName.postMessage(fullParam) 21 | } 22 | var reqs = {}; 23 | window.appHost.on = function(_action, _callback) { 24 | reqs[_action + ""] = _callback; 25 | } 26 | window.appHost.__fire = function(_action, _data) { 27 | var func = reqs[_action + ""]; 28 | if (typeof func == 'function') { 29 | func(_data); 30 | } 31 | } 32 | window.appHost.__callback = function(_callbackKey, _param) { 33 | var func = callbackPool[_callbackKey]; 34 | if (typeof func == 'function') { 35 | func(_param); 36 | // 释放,只用一次 37 | callbackPool[_callbackKey] = nil; 38 | } 39 | } 40 | }(window); 41 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Timing.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Timing.m 3 | // AppHost 4 | // 5 | // Created by liang on 2019/4/2. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AppHostViewController+Timing.h" 10 | #import 11 | 12 | @implementation AppHostViewController (Timing) 13 | 14 | #ifdef AH_DEBUG 15 | 16 | - (void)mark:(NSString *)markName 17 | { 18 | NSMutableDictionary *marks = self.marks; 19 | if (marks == nil) { 20 | marks = [NSMutableDictionary dictionaryWithCapacity:10]; 21 | self.marks = marks; 22 | } 23 | 24 | [marks setObject:@(NOW_TIME) forKey:markName]; 25 | } 26 | 27 | - (void)measure:(NSString *)endMarkName to:(NSString *)startMark; 28 | { 29 | long long time = [[self.marks objectForKey:startMark] longLongValue]; 30 | AHLog(@"[Timing] %@ ~ %@ 耗时共 %f",endMarkName, startMark, NOW_TIME - time); 31 | } 32 | #else 33 | 34 | - (void)mark:(NSString *)markName{}; 35 | - (void)measure:(NSString *)endMarkName to:(NSString *)startMark{}; 36 | 37 | #endif 38 | 39 | #pragma mark - getter 40 | 41 | - (void)setMarks:(NSMutableDictionary *)marks 42 | { 43 | objc_setAssociatedObject(self, @selector(setMarks:), marks, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 44 | } 45 | 46 | - (NSMutableDictionary *)marks 47 | { 48 | return objc_getAssociatedObject(self, @selector(setMarks:)); 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /AppHost/RemoteDebug/profile/pageTiming.js: -------------------------------------------------------------------------------- 1 | !function() { 2 | if (!window.__profiler || window.__profiler.scriptLoaded !== true) { 3 | var d = document, 4 | h = d.getElementsByTagName('head')[0], 5 | 6 | l = d.createElement('div'), 7 | c = function () { 8 | if (l) { 9 | d.body.removeChild(l); 10 | } 11 | window.__profiler = window.__profiler || new __Profiler(); 12 | window.__profiler.init(); 13 | __profiler.scriptLoaded = true; 14 | }, 15 | t = new Date(); 16 | 17 | l.style.cssText = 'z-index:999;position:fixed;top:10px;left:10px;display:inline;width:auto;font-size:14px;line-height:1.5em;font-family:Helvetica,Calibri,Arial,sans-serif;text-shadow:none;padding:3px 10px 0;background:#FFFDF2;box-shadow:0 0 0 3px rgba(0,0,0,.25),0 0 5px 5px rgba(0,0,0,.25); border-radius:1px'; 18 | l.innerHTML = 'Just a moment'; 19 | d.body.appendChild(l); 20 | l.style.display = 'none'; 21 | // 当处于 AppHost 环境时,接收命令显示。 22 | if (window.appHost) { 23 | window.appHost.on('requestToTiming', function () { 24 | l.style.display = 'block'; 25 | c(); 26 | }); 27 | } else { 28 | c(); 29 | } 30 | } else if (window.__profiler instanceof __Profiler) { 31 | window.__profiler.init(); 32 | } 33 | }(this); 34 | -------------------------------------------------------------------------------- /AppHost/Core/AHHTTPSchemeTaskDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AHHTTPSchemeTaskDelegate.m 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/11. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import "AHHTTPSchemeTaskDelegate.h" 10 | 11 | @implementation AHHTTPSchemeTaskDelegate 12 | 13 | - (void)webView:(WKWebView *)webView startURLSchemeTask:(nonnull id)urlSchemeTask 14 | { 15 | NSURLRequest *request = urlSchemeTask.request; 16 | 17 | [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 18 | if (data == nil) { 19 | NSError *err = [[NSError alloc] initWithDomain:@"自定义的资源无法解析" code:-4003 userInfo:nil]; 20 | [urlSchemeTask didFailWithError:err]; 21 | } else { 22 | NSURLResponse *response = [[NSURLResponse alloc] initWithURL:urlSchemeTask.request.URL MIMEType:@"text/html" expectedContentLength:data.length textEncodingName:nil]; 23 | [urlSchemeTask didReceiveResponse:response]; 24 | [urlSchemeTask didReceiveData:data]; 25 | [urlSchemeTask didFinish]; 26 | } 27 | }] resume]; 28 | } 29 | 30 | - (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask { 31 | // 32 | AHLog(@"%@", NSStringFromSelector(_cmd)); 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /AppHost/Core/Helper/AHWebViewScrollPositionManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKWebViewScrollPositionManager.m 3 | 4 | // 5 | // Created by liang on 02/05/2017. 6 | // Copyright © 2017 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AHWebViewScrollPositionManager.h" 10 | 11 | /** 12 | 保持页面的当前滚动位置 13 | */ 14 | static NSMutableDictionary *positionHolder = nil; 15 | 16 | @implementation AHWebViewScrollPositionManager 17 | 18 | + (instancetype)sharedInstance 19 | { 20 | static AHWebViewScrollPositionManager *_sharedManager = nil; 21 | static dispatch_once_t onceToken; 22 | 23 | dispatch_once(&onceToken, ^{ 24 | _sharedManager = [[self alloc] init]; 25 | positionHolder = [NSMutableDictionary dictionaryWithCapacity:10]; 26 | }); 27 | 28 | return _sharedManager; 29 | } 30 | 31 | - (void)cacheURL:(NSURL *)url position:(CGFloat)y 32 | { 33 | // 记录2个网站的位置,达到 quote,清空 34 | if (positionHolder.allKeys.count > 2) { 35 | [positionHolder removeAllObjects]; 36 | } 37 | if (url) { 38 | [positionHolder setObject:@(y) forKey:url]; 39 | } 40 | } 41 | 42 | - (CGFloat)positionForCacheURL:(NSURL *)url 43 | { 44 | CGFloat y = 0; 45 | if (url) { 46 | y = [[positionHolder objectForKey:url] floatValue]; 47 | } 48 | return y; 49 | } 50 | 51 | - (void)emptyURLCache:(NSURL *)url 52 | { 53 | [positionHolder removeObjectForKey:url]; 54 | } 55 | 56 | - (void)clearAllCache 57 | { 58 | [positionHolder removeAllObjects]; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/Objective-C-HMTL-Parser/README.md: -------------------------------------------------------------------------------- 1 | 1. Open Your project in XCode and drag and drop all .h & .m Files into an appropriate folder 2 | 2. In the project settings add "/usr/include/libxml2" to the "header search paths" field 3 | 3. Ctrl Click the Frameworks group choose Add -> Existing Frameworks and from the list choose libxml2.dylib 4 | 5 | Example Usage 6 | ============= 7 | ```objc 8 | NSError *error = nil; 9 | NSString *html = 10 | @"
    " 11 | "
  • " 12 | "
  • " 13 | "
" 14 | "Hello World 1" 15 | "Hello World 2"; 16 | HTMLParser *parser = [[HTMLParser alloc] initWithString:html error:&error]; 17 | 18 | if (error) { 19 | NSLog(@"Error: %@", error); 20 | return; 21 | } 22 | 23 | HTMLNode *bodyNode = [parser body]; 24 | 25 | NSArray *inputNodes = [bodyNode findChildTags:@"input"]; 26 | 27 | for (HTMLNode *inputNode in inputNodes) { 28 | if ([[inputNode getAttributeNamed:@"name"] isEqualToString:@"input2"]) { 29 | NSLog(@"%@", [inputNode getAttributeNamed:@"value"]); //Answer to first question 30 | } 31 | } 32 | 33 | NSArray *spanNodes = [bodyNode findChildTags:@"span"]; 34 | 35 | for (HTMLNode *spanNode in spanNodes) { 36 | if ([[spanNode getAttributeNamed:@"class"] isEqualToString:@"spantext"]) { 37 | NSLog(@"%@", [spanNode rawContents]); //Answer to second question 38 | } 39 | } 40 | 41 | [parser release]; 42 | ``` 43 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHPrefetchResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // AHPrefetchResponse.m 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/12. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import "AHPrefetchResponse.h" 10 | #import "AHPrefetchMonitor.h" 11 | #import "AppHostViewController.h" 12 | #import "AppHostViewController+Scripts.h" 13 | 14 | @implementation AHPrefetchResponse 15 | + (NSDictionary *)supportActionList 16 | { 17 | return @{ 18 | @"fetchDataForHash_" : @"1" 19 | }; 20 | } 21 | 22 | ah_doc_begin(fetchDataForHash_, "H5 请求获取对应 URL 和 api 的客户端缓存") 23 | ah_doc_param(hash, "数字,是由代码样例中的函数生成的 hash 值 int32_t") 24 | ah_doc_code((function strHashCode(str) { 25 | var hash = 0, i, chr; 26 | if (str.length === 0) return hash; 27 | for (i = 0; i < str.length; i++) { 28 | chr = str.charCodeAt(i); 29 | hash = ((hash << 5) - hash) + chr; 30 | hash |= 0; // Convert to 32bit integer 31 | } 32 | return hash; 33 | })()) 34 | ah_doc_code_expect("在回调里返回此 api 请求的缓存值,可能为空,由 status 字段标示") 35 | ah_doc_end 36 | - (void)fetchDataForHash:(int32_t)hash 37 | { 38 | static NSString *callbackKey = @"returnPrefetchData"; 39 | NSDictionary *ret = [self.appHost.prefetchLoaderDelegate cacheForHash:hash]; 40 | if (ret) { 41 | [self.appHost fire:callbackKey param:@{ 42 | @"status": @(AHPrefetchStatusSucc), 43 | @"prefetchData": ret 44 | }]; 45 | } else { 46 | [self.appHost fire:callbackKey param:@{ 47 | @"status": @(AHPrefetchStatusFailed) 48 | }]; 49 | } 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Scripts.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Scripts.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/23. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AppHostViewController.h" 10 | 11 | @class WKUserContentController; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface AppHostViewController (Scripts) 16 | 17 | /** 18 | * 调用 callback 的函数,这个函数是 js 端调用方法时,注册在 js 端的 block。 19 | * 这里传入的第一个参数是 和这个 js 端 block 相关联的 key。js 根据这个 key 找到这个 block 并且执行 20 | */ 21 | - (void)fireCallback:(NSString *)actionName param:(NSDictionary *)paramDict; 22 | /** 23 | * 对应,监听了事件的接口的调用 24 | */ 25 | - (void)fire:(NSString *)actionName param:(NSDictionary *)paramDict; 26 | 27 | /** 28 | 无返回值的执行 js 代码 29 | 30 | @param javaScriptString 可执行的 js 代码 31 | */ 32 | - (void)executeJavaScriptString:(NSString *)javaScriptString; 33 | /** 34 | 需要返回值的 js 代码。可以返回例如 document 之类的,JSValue 无法映射的数据对象 35 | 36 | @param jsCode 可执行的 js 代码,注意:如果有引号,需要使用双引号。 37 | @param completion 返回的回调 38 | */ 39 | - (void)evalExpression:(NSString *)jsCode completion:(void (^)(id result, NSString *err))completion; 40 | /** 41 | 设置在 userscript 里要加载的脚本。如果已经打开了 webview,则这些设置需要在下次执行时生效 42 | 43 | @param script 注入的脚本,string\ url,两种类型 44 | @param injectTime 注入时机 45 | @param key 这段脚本的标识,为了后续的删除 46 | */ 47 | + (void)prepareJavaScript:(id)script when:(WKUserScriptInjectionTime)injectTime key:(NSString *)key; 48 | + (void)removeJavaScriptForKey:(NSString *)key; 49 | 50 | #pragma mark - like private 51 | - (void)insertData:(NSDictionary *)json intoPageWithVarName:(NSString *)appProperty; 52 | 53 | - (void)injectScriptsToUserContent:(WKUserContentController *)userContent; 54 | 55 | @end 56 | 57 | NS_ASSUME_NONNULL_END 58 | -------------------------------------------------------------------------------- /AppHost/Boost/Prefetch/AHPrefetchMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHPrefetchMonitor.h 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/12. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AHLogProviderProtocol.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | //int TYPE_MISS = 1; //当前接口未命中配置表 14 | //int TYPE_PROCESSING = 2; //接口正在请求中 15 | //int TYPE_FAILED = 3; //接口请求失败 16 | //int TYPE_SUCCESS = 4; //接口请求成功, 可以从prefetchData中获取数据 17 | //int TYPE_ERROR = 5; //前端jsb传递的参数错误 18 | 19 | typedef NS_ENUM(NSInteger, AHPrefetchStatus) { 20 | AHPrefetchStatusDefault = 0, 21 | AHPrefetchStatusUnHit = 1, 22 | AHPrefetchStatusInProcessing, 23 | AHPrefetchStatusFailed, 24 | AHPrefetchStatusSucc, 25 | AHPrefetchStatusError 26 | }; 27 | @interface AHPrefetchMetrics : NSObject 28 | 29 | @property (nonatomic, assign) int32_t hashKey; 30 | @property (nonatomic, strong) NSString *url; 31 | 32 | @property (nonatomic, strong) NSString *api; 33 | @property (nonatomic, strong) NSString *method; 34 | // 请求预加载的时间 35 | @property (nonatomic, assign) NSInteger loadTime; 36 | 37 | // 请求数据返回的时间,某个 api 38 | @property (nonatomic, assign) NSInteger readyTime; 39 | @property (nonatomic, assign) NSInteger loadFailTime; 40 | 41 | // h5 请求数据的时间, 42 | @property (nonatomic, assign) NSInteger fetchTime; 43 | 44 | @end 45 | 46 | @interface AHPrefetchMonitor : NSObject 47 | + (instancetype)sharedInstance; 48 | 49 | /// 设置统计日志上传接口,可由外部定义 50 | /// @param logger 自定义 log 系统接口 51 | - (void)setLogger:(id)logger; 52 | 53 | - (void)markLoadTime:(NSInteger)realTime forHash:(int32_t)hash url:(NSString *)url api:(NSString *)api; 54 | 55 | - (void)markReadyTime:(int32_t)hash; 56 | 57 | - (void)markLoadFailTime:(int32_t)hash; 58 | 59 | - (void)markFetchTime:(int32_t)hash; 60 | @end 61 | 62 | 63 | NS_ASSUME_NONNULL_END 64 | -------------------------------------------------------------------------------- /AppHost/Core/URLChecker/AHAppWhiteListParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKAppWhiteListParser.m 3 | 4 | // 5 | // Created by hite on 4/21/16. 6 | // Copyright © 2016 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AHAppWhiteListParser.h" 10 | 11 | @implementation AHAppWhiteListParser 12 | 13 | + (instancetype)sharedManager 14 | { 15 | static AHAppWhiteListParser *_sharedManager = nil; 16 | static dispatch_once_t onceToken; 17 | 18 | dispatch_once(&onceToken, ^{ 19 | _sharedManager = [[self alloc] init]; 20 | }); 21 | 22 | return _sharedManager; 23 | } 24 | 25 | 26 | - (NSDictionary *)parserFileContent:(NSString *)fileContents{ 27 | NSCharacterSet *newlineCharSet = [NSCharacterSet newlineCharacterSet]; 28 | NSArray *lines = [fileContents componentsSeparatedByCharactersInSet:newlineCharSet]; 29 | //开始解析 30 | NSMutableDictionary *tree = [[NSMutableDictionary alloc] initWithCapacity:10]; 31 | NSMutableArray __block *currentList = nil; 32 | [lines enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) { 33 | // 34 | if ([obj hasPrefix:@"#"]) { 35 | // 注释 36 | }else if ([obj hasSuffix:@":"]){ 37 | // 分组 38 | NSString *key = [obj substringToIndex:obj.length-1]; 39 | if ([tree objectForKey:key] == nil){ 40 | [tree setObject:[[NSMutableArray alloc] initWithCapacity:9] forKey:key]; 41 | } 42 | currentList = [tree objectForKey:key]; 43 | }else if (obj.length > 0){ 44 | if (currentList) { 45 | // 将.号转化为\. 46 | [currentList addObject:obj]; 47 | }else{ 48 | // 没有分组的被丢弃 49 | NSLog(@" %@ was discard!", obj); 50 | } 51 | } 52 | }]; 53 | 54 | return tree; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /AppHost/Boost/Preloader/AHWebViewPreLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // AHWebViewPreLoader.h 3 | // 4 | // 5 | // Created by liang on 2019/7/16. 6 | // Copyright © 2019 Smily.Co. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppHostEnum.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | // 表示目前本地的 .json 文件的版本。如果服务器较旧则不返回新配置 15 | static const int kAHPreloadResourceVersion = 1; 16 | extern NSString * const kPreloadResourceConfCacheKey; 17 | 18 | typedef NSDictionary *_Nonnull(^bFetchConfig)(int version); 19 | 20 | @interface AHWebViewPreLoader : NSObject 21 | 22 | + (instancetype)defaultLoader; 23 | 24 | /** 25 | 从服务器下载最新的配置到 ud 里. 传入当前版本,返回新的配置,配置样式如下: 26 | 27 | { 28 | "domain": "https://m.you.163.com", // 是 html 加载时的地址,重要的是 host 部分 29 | 30 | // 下面的属性中,如 scripts 的地址为 相对地址,和 baseURL 拼接形成完成,如, 31 | // baseURL = "https://yanxuan-static.nosdn.127.net". 32 | // Scripts = ["/xm/a.js"], 33 | // 实际得到的地址是:https://yanxuan-static.nosdn.127.net/xm/a.js 34 | "baseURL": "https://yanxuan-static.nosdn.127.net", 35 | "scripts" : [ 36 | "/hxm/yanxuan-wap/p/20161201/js/base-14b63f4707.js", 37 | "/hxm/yanxuan-jssdk/common/js/jweixin-1.3.2.js", 38 | "/hxm/yanxuan-wap/p/20161201/js/dist/index/index-c0616e142a.page.js", 39 | "/hxm/yanxuan-wap/p/20161201/js/dist/webview/itemDetail/itemDetail-2424073b4e.page.js" 40 | ], 41 | "styles": [ 42 | "/hxm/yanxuan-wap/p/20161201/style/css/style-05d5040aba.css" 43 | ], 44 | "images": [ 45 | "https://yanxuan-static.nosdn.127.net/hxm/yanxuan-wap/p/20161201/style/img/icon-normal/goToTop-f502426678.png" 46 | ], 47 | "fonts" : [], // 预加载的字体,可以是数组 48 | "html" : "" // 预加载的 HTML,为了控制资源,只能加载一个 49 | } 50 | */ 51 | - (void)updateConfig:(bFetchConfig)fetchConfig; 52 | 53 | /** 54 | 在新开的 window 里去下载尝试下载关键资源。 55 | 如果有以前旧配置,使用旧配置下载。 56 | */ 57 | - (void)loadResources; 58 | 59 | @end 60 | 61 | NS_ASSUME_NONNULL_END 62 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Extend.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Extend.h 3 | // AppHost 4 | // 5 | // Created by liang on 2019/4/16. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | 这个分类的意义在于为 AppHost 调用方, 15 | 有对 webview 什么周期有特殊需要的时候,可以继承 AppHostViewController,并重载相应的方法实现自有逻辑。 16 | 注意:一旦重载之后,在方法体里,需要调用 super 方法。 17 | 18 | 重要提示: 为了让调用方可以自定义逻辑,有两种方式, 19 | 一种是开放 super,使用继承的方式; 20 | 一种是新增一个代理类,在不同的回调里,让调用方自行实现需要的方法。 21 | 22 | 此外,我们也使用过 webviewjsbridge 对 UIWebview 和 WKWebview 上面封装的方式来提供接口分离,解耦。 23 | 然后在实际的使用过程中,发现这是一种非常丑陋的提供灵活的方式。 24 | 综合考虑,我们使用第一种,更灵活,缺点是开放了较多的接口,所以如非必要,不要继承 AppHostViewController 类。 25 | */ 26 | @interface AppHostViewController (Extend) 27 | 28 | - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler; 29 | 30 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler; 31 | 32 | - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation; 33 | 34 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(nonnull WKNavigationResponse *)navigationResponse decisionHandler:(nonnull void (^)(WKNavigationResponsePolicy))decisionHandler; 35 | 36 | - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation; 37 | 38 | - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation; 39 | 40 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation; 41 | 42 | - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error; 43 | 44 | - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error; 45 | 46 | @end 47 | 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Dispatch.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Dispatch.m 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/23. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AppHostViewController+Dispatch.h" 10 | #import "AppHostViewController+Scripts.h" 11 | #import "AppHostViewController+Utils.h" 12 | #import "AHResponseManager.h" 13 | 14 | @implementation AppHostViewController (Dispatch) 15 | 16 | #pragma mark - core 17 | - (void)dispatchParsingParameter:(NSDictionary *)contentJSON 18 | { 19 | // 增加对异常参数的catch 20 | @try { 21 | NSDictionary *paramDict = [contentJSON objectForKey:kAHParamKey]; 22 | NSString *callbackKey = [contentJSON objectForKey:@"callbackKey"]; 23 | [self callNative:[contentJSON objectForKey:kAHActionKey] parameter:paramDict callbackKey:callbackKey]; 24 | 25 | [[NSNotificationCenter defaultCenter] postNotificationName:kAppHostInvokeRequestEvent object:contentJSON]; 26 | } @catch (NSException *exception) { 27 | [self showTextTip:@"H5接口异常"]; 28 | AHLog(@"h5接口解析异常,接口数据:%@", contentJSON); 29 | } @finally { 30 | } 31 | } 32 | 33 | #pragma mark - public 34 | // 延迟初始化; 短路判断 35 | - (BOOL)callNative:(NSString *)action parameter:(NSDictionary *)paramDict 36 | { 37 | return [self callNative:action parameter:paramDict callbackKey:nil]; 38 | } 39 | 40 | #pragma mark - private 41 | - (BOOL)callNative:(NSString *)action parameter:(NSDictionary *)paramDict callbackKey:(NSString *)key 42 | { 43 | AHResponseManager *rm = [AHResponseManager defaultManager]; 44 | NSString *actionSig = [rm actionSignature:action withParam:paramDict withCallback:key.length > 0]; 45 | id response = [rm responseForActionSignature:actionSig withAppHost:self]; 46 | // 47 | if (response == nil || ![response handleAction:action withParam:paramDict callbackKey:key]) { 48 | NSString *errMsg = [NSString stringWithFormat:@"action (%@) not supported yet.", action]; 49 | AHLog(@"action (%@) not supported yet.", action); 50 | [self fire:@"NotSupported" param:@{ 51 | @"error": errMsg 52 | }]; 53 | return NO; 54 | } else { 55 | return YES; 56 | } 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Requests/GCDWebServerFileRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerRequest.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body 34 | * of the HTTP request to a file on disk. 35 | */ 36 | @interface GCDWebServerFileRequest : GCDWebServerRequest 37 | 38 | /** 39 | * Returns the path to the temporary file containing the request body. 40 | * 41 | * @warning This temporary file will be automatically deleted when the 42 | * GCDWebServerFileRequest is deallocated. If you want to preserve this file, 43 | * you must move it to a different location beforehand. 44 | */ 45 | @property(nonatomic, readonly) NSString* temporaryPath; 46 | 47 | @end 48 | 49 | NS_ASSUME_NONNULL_END 50 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKAppHost.h 3 | // 4 | // Created by liang on 05/01/2018. 5 | // Copyright © 2018 smilly.co All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @import WebKit; 11 | @class AppHostViewController; 12 | 13 | static NSString *const kAppHostURLScheme = @"apphost"; 14 | static NSString *const kAppHostURLProtocal = @"apphost://"; 15 | static NSString *const kAppHostURLImageHost = @"image.apphost.hite.me"; 16 | static NSString *const kAppHostURLScriptHost = @"js.apphost.hite.me"; 17 | static NSString *const kAppHostURLStyleHost = @"css.apphost.hite.me"; 18 | 19 | #define AppHostURLScriptServer [kAppHostURLProtocal stringByAppendingString:kAppHostURLScriptHost] 20 | #define AppHostURLStyleServer [kAppHostURLProtocal stringByAppendingString:kAppHostURLStyleHost] 21 | #define AppHostURLImageServer [kAppHostURLProtocal stringByAppendingString:kAppHostURLImageHost] 22 | 23 | @protocol AppHostProtocol 24 | 25 | // 以下为 从AppHostViewController 里获得的 只读类属性 26 | @property (nonatomic, weak, readonly) UINavigationController *navigationController; 27 | 28 | @property (nonatomic, weak, readonly) WKWebView *webView; 29 | 30 | @property (nonatomic, weak, readonly) AppHostViewController *appHost; 31 | 32 | @required 33 | 34 | - (instancetype)initWithAppHost:(AppHostViewController *)appHost; 35 | 36 | /** 37 | 尝试处理来自 h5 的请求,如果不能处理,则返回 NO。 38 | 39 | @param action h5 的 actionName 40 | @param paramDict 本次请求的参数 41 | @param callbackKey js 端匿名回调 42 | @return YES 表示可以处理,已处理; 43 | */ 44 | - (BOOL)handleAction:(NSString *)action withParam:(NSDictionary *)paramDict callbackKey:(NSString *)callbackKey; 45 | 46 | /** 47 | 类方法。表示当前请类型是否支持 48 | 49 | @param ActionSignature 表示 action 的签名,action 的名词加上"_","$",如 alert_$ 50 | @return YES 表示支持,请注意 51 | */ 52 | + (BOOL)isSupportedActionSignature:(NSString *)ActionSignature; 53 | 54 | /** 55 | 返回接口的支持情况, 申明为类方法是为了用同步的方法 返回给 appHost,作为 JS 的属性。 56 | 其中 key 值后面的下划线 _ 表示是否需要参数。下划线 _ 数量表示有 param;$ 表示有 callback。在 AppHost 的接口中,只有 4 种接口; 57 | @return 形如, 58 | { 59 | @"alert": @"1",// 无参数 60 | @"alert$": @"1",// 有1参数,名字是 callbackKey 61 | @"alert_": @"1",// 有1参数,名字是 paramDict 62 | @"alert_$": @"1",// 有2参数, paramDict,callbackKey 63 | } 64 | */ 65 | + (NSDictionary *)supportActionList; 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerDataRequest.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest 34 | * parses the body of the HTTP request as a URL encoded form using 35 | * GCDWebServerParseURLEncodedForm(). 36 | */ 37 | @interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest 38 | 39 | /** 40 | * Returns the unescaped control names and values for the URL encoded form. 41 | * 42 | * The text encoding used to interpret the data is extracted from the 43 | * "Content-Type" header or defaults to UTF-8. 44 | */ 45 | @property(nonatomic, readonly) NSDictionary* arguments; 46 | 47 | /** 48 | * Returns the MIME type for URL encoded forms 49 | * i.e. "application/x-www-form-urlencoded". 50 | */ 51 | + (NSString*)mimeType; 52 | 53 | @end 54 | 55 | NS_ASSUME_NONNULL_END 56 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHBuiltInResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKBuiltInResponse.m 3 | 4 | // 5 | // Created by liang on 06/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AHBuiltInResponse.h" 10 | #import "AppHostViewController.h" 11 | 12 | @implementation AHBuiltInResponse 13 | 14 | + (NSDictionary *)supportActionList 15 | { 16 | return @{ 17 | @"toast_" : @"1", 18 | @"showLoading_" : @"1", 19 | @"hideLoading" : @"1", 20 | @"enablePageBounce_" : @"1" 21 | }; 22 | } 23 | 24 | #pragma mark - inner 25 | 26 | ah_doc_begin(showLoading_, "loading 的 HUD 动画,这里是AppHost默认实现显示。") 27 | ah_doc_param(text, "字符串,设置和 loading 动画一起显示的文案") 28 | ah_doc_code(window.appHost.invoke("showLoading",{"text":"请稍等..."})) 29 | ah_doc_code_expect("在屏幕上出现 loading 动画,多次调用此接口,不应该出现多个") 30 | ah_doc_end 31 | - (void)showLoading:(NSDictionary *)paramDict 32 | { 33 | NSString *tip = [paramDict objectForKey:@"text"]; 34 | NSLog(@"Info: 正在显示 Loading 提示: %@,请使用本 App 的的 HUD 接口实现,以保持一致体验", tip); 35 | } 36 | 37 | ah_doc_begin(hideLoading, "隐藏 loading 的 HUD 动画,这里是AppHost默认实现显示。") 38 | ah_doc_code(window.appHost.invoke("hideLoading")) 39 | ah_doc_code_expect("在有 loading 动画的情况下,调用此接口,会隐藏 loading。") 40 | ah_doc_end 41 | - (void)hideLoading 42 | { 43 | NSLog(@"Info: 关闭显示 HUD ,请使用本 App 的的 HUD 接口实现,以保持一致体验"); 44 | } 45 | 46 | ah_doc_begin(toast_, "显示居中的提示,过几秒后消失,这里是AppHost默认实现显示。") 47 | ah_doc_param(text, "字符串,显示的文案,可多行") 48 | ah_doc_code(window.appHost.invoke("toast",{"text":"请稍等..."})) 49 | ah_doc_code_expect("在屏幕上出现 '请稍等...',多次调用此接口,不应该出现多个") 50 | ah_doc_end 51 | - (void)toast:(NSDictionary *)paramDict 52 | { 53 | CGFloat delay = [[paramDict objectForKey:@"delay"] floatValue]; 54 | [self showTextTip:[paramDict objectForKey:@"text"] delay:delay]; 55 | } 56 | 57 | - (void)showTextTip:(NSString *)tip delay:(CGFloat)delay 58 | { 59 | NSLog(@"Info: 正在显示 Toast 提示: %@, %f秒消失,请使用本 App 的的 HUD 接口实现,以保持一致体验", tip, delay); 60 | } 61 | 62 | ah_doc_begin(enablePageBounce_, "容许触发 webview 下拉弹回的动画,传入 false 表示不容许;这个效果是 iOS 独有的") 63 | ah_doc_param(enabled, "布尔值, true 表示开启,false 表示关闭") 64 | ah_doc_code(window.appHost.invoke("enablePageBounce",{"enabled":false})) 65 | ah_doc_code_expect("本测试页面在滑动到底部或顶部时,没有 bounce 效果,在执行之前,尝试滑动底部,会出现 bounce 效果。") 66 | ah_doc_end 67 | - (void)enablePageBounce:(NSDictionary *)paramDict 68 | { 69 | BOOL bounce = [[paramDict objectForKey:@"enabled"] boolValue]; 70 | self.webView.scrollView.bounces = bounce; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "GCDWebServerPrivate.h" 33 | 34 | @implementation GCDWebServerURLEncodedFormRequest 35 | 36 | + (NSString*)mimeType { 37 | return @"application/x-www-form-urlencoded"; 38 | } 39 | 40 | - (BOOL)close:(NSError**)error { 41 | if (![super close:error]) { 42 | return NO; 43 | } 44 | 45 | NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); 46 | NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; 47 | _arguments = GCDWebServerParseURLEncodedForm(string); 48 | return YES; 49 | } 50 | 51 | - (NSString*)description { 52 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 53 | [description appendString:@"\n"]; 54 | for (NSString* argument in [[_arguments allKeys] sortedArrayUsingSelector:@selector(compare:)]) { 55 | [description appendFormat:@"\n%@ = %@", argument, [_arguments objectForKey:argument]]; 56 | } 57 | return description; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Requests/GCDWebServerDataRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerRequest.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body 34 | * of the HTTP request in memory. 35 | */ 36 | @interface GCDWebServerDataRequest : GCDWebServerRequest 37 | 38 | /** 39 | * Returns the data for the request body. 40 | */ 41 | @property(nonatomic, readonly) NSData* data; 42 | 43 | @end 44 | 45 | @interface GCDWebServerDataRequest (Extensions) 46 | 47 | /** 48 | * Returns the data for the request body interpreted as text. If the content 49 | * type of the body is not a text one, or if an error occurs, nil is returned. 50 | * 51 | * The text encoding used to interpret the data is extracted from the 52 | * "Content-Type" header or defaults to UTF-8. 53 | */ 54 | @property(nonatomic, readonly, nullable) NSString* text; 55 | 56 | /** 57 | * Returns the data for the request body interpreted as a JSON object. If the 58 | * content type of the body is not JSON, or if an error occurs, nil is returned. 59 | */ 60 | @property(nonatomic, readonly, nullable) id jsonObject; 61 | 62 | @end 63 | 64 | NS_ASSUME_NONNULL_END 65 | -------------------------------------------------------------------------------- /AppHost/Core/Helper/AppHostCookie.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKAppHostCookie.m 3 | 4 | // 5 | // Created by liang on 06/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AppHostCookie.h" 10 | #import "AppHostEnum.h" 11 | 12 | @implementation AppHostCookie 13 | 14 | + (NSMutableArray *)cookieJavaScriptArray 15 | { 16 | NSMutableArray *cookieStrings = [[NSMutableArray alloc] init]; 17 | //取出cookie 18 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 19 | for (NSHTTPCookie *cookie in cookieStorage.cookies) { 20 | // 21 | NSString *excuteJSString = [NSString stringWithFormat:@"%@=%@;", cookie.name, cookie.value]; 22 | if (cookie.domain.length > 0) { 23 | excuteJSString = [excuteJSString stringByAppendingFormat:@"domain=%@;", cookie.domain]; 24 | } 25 | if (cookie.path.length > 0) { 26 | excuteJSString = [excuteJSString stringByAppendingFormat:@"path=%@;", cookie.path]; 27 | } 28 | if (cookie.expiresDate != nil) { 29 | excuteJSString = [excuteJSString stringByAppendingFormat:@"expires=%@;", cookie.expiresDate]; 30 | } 31 | if (cookie.secure) { 32 | excuteJSString = [excuteJSString stringByAppendingString:@"secure=true"]; 33 | } 34 | 35 | [cookieStrings addObject:excuteJSString]; 36 | AHLog(@"cookie to be set = %@", excuteJSString); 37 | } 38 | 39 | return cookieStrings; 40 | } 41 | 42 | static WKProcessPool *_sharedManager = nil; 43 | // 当从未登录到登录时,这个状态需要置为 No 44 | static BOOL kLoginCookieHasBeenSynced = NO; 45 | 46 | + (WKProcessPool *)sharedPoolManager 47 | { 48 | 49 | static dispatch_once_t onceToken; 50 | 51 | dispatch_once(&onceToken, ^{ 52 | _sharedManager = [[WKProcessPool alloc] init]; 53 | // 监听 urs 登出的事件。 54 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didLogout:) name:kAHLogoutNotification object:nil]; 55 | 56 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUserLoginSucss:) name:kAHLoginSuccessNotification object:nil]; 57 | }); 58 | 59 | return _sharedManager; 60 | } 61 | 62 | + (void)didLogout:(id)notif 63 | { 64 | // 清空旧的 WKProcessPool 65 | AHLog(@"didLogout, old pool is destoryed"); 66 | _sharedManager = [[WKProcessPool alloc] init]; 67 | } 68 | 69 | #pragma mark - cookie sync part 70 | 71 | + (void)didUserLoginSucss:(id)notif 72 | { 73 | kLoginCookieHasBeenSynced = NO; 74 | } 75 | 76 | + (void)setLoginCookieHasBeenSynced:(BOOL)synced 77 | { 78 | kLoginCookieHasBeenSynced = synced; 79 | } 80 | 81 | + (BOOL)loginCookieHasBeenSynced 82 | { 83 | return kLoginCookieHasBeenSynced; 84 | } 85 | @end 86 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController.h 3 | 4 | // 5 | // Created by hite on 9/22/15. 6 | // Copyright © 2015 smilly.co All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppHostProtocol.h" 11 | #import "AHSchemeTaskDelegate.h" 12 | #import "AppHostEnum.h" 13 | 14 | #import "AHPrefetchLoaderProtocol.h" 15 | 16 | static NSString *kAppHostInvokeRequestEvent = @"kAppHostInvokeRequestEvent"; 17 | static NSString *kAppHostInvokeResponseEvent = @"kAppHostInvokeResponseEvent"; 18 | 19 | @class AppHostViewController; 20 | 21 | /** 22 | 监听 Response 里的事件; 23 | */ 24 | @protocol AppHostViewControllerDelegate 25 | 26 | - (void)onResponseEventOccurred:(NSString *)eventName response:(id)response; 27 | 28 | @end 29 | 30 | @interface AppHostViewController : UIViewController 31 | 32 | 33 | @property (nonatomic, copy) NSString *pageTitle; 34 | 35 | /** 36 | 当使用 url 地址加载页面时,url 代表了初始的 url。当载入初始 url 后,页面的地址还可能发生变化,此时不等于此 url。 37 | */ 38 | @property (nonatomic, copy) NSString *url; 39 | /** 40 | * 右上角的文案 41 | */ 42 | @property (nonatomic, copy) NSString *rightActionBarTitle; 43 | 44 | // 45 | @property (nonatomic, strong, readonly) WKWebView *webView; 46 | 47 | /** 48 | 定制状态栏的配色 49 | */ 50 | @property (nonatomic, assign) UIStatusBarStyle navBarStyle; 51 | /** 52 | 不容许进度条 53 | */ 54 | @property (nonatomic, assign) BOOL disabledProgressor; 55 | 56 | /** 57 | 取消记住上次浏览历史的特性 58 | */ 59 | @property (nonatomic, assign) BOOL disableScrollPositionMemory; 60 | /** 61 | * 指,当点击导航栏的back按钮时候,执行的跳转,并且这个跳转到这个链接 62 | */ 63 | @property (nonatomic, strong) NSDictionary *backPageParameter; 64 | 65 | // 处理 Response 内部发送的事件,这些事件,除了 h5 关心之外,可能 native 本身也很关心 66 | @property (nonatomic, weak) id appHostDelegate; 67 | //核心的函数分发机制。可以继承, 68 | 69 | /** 70 | 是否是被presented 71 | */ 72 | @property (nonatomic, assign) BOOL fromPresented; 73 | 74 | @property (nonatomic, strong, readonly) AHSchemeTaskDelegate *taskDelegate; 75 | 76 | /// 设置全局的 preLoader,需要遵循 AHPrefetchLoaderProtocol 协议 77 | @property (nonatomic, weak) id prefetchLoaderDelegate; 78 | 79 | #pragma mark - 使用缓存渲染界面 80 | /** 81 | 加载本地 html 资源,支持发送 xhr 请求 82 | 83 | @param url 打开的文件路径 84 | @param baseDomain 发送 xhr 请求的主域名地址,如 http://you.163.com 85 | */ 86 | - (void)loadLocalFile:(NSURL *)url domain:(NSString *)baseDomain; 87 | 88 | /** 89 | 加载本地文件夹。文件夹只支持 HTML,JS,CSS 文件。 90 | 在 iOS 11 以上使用 taskscheme,iOS 8+ 以上使用文件合并,不支持本地图片; 91 | 92 | @param fileName 主 HTML 文件的文件名,是个相对路径。 html 文件里应用的内部 js、css 文件都是相对于 directory 参数的 93 | @param directory 相对路径,包含 HTML,JS,CSS 文件 94 | @param baseDomain 为了解决相对路径 发送 xhr 请求的主域名地址,如 http://you.163.com 95 | */ 96 | - (void)loadIndexFile:(NSString *)fileName inDirectory:(NSURL *)directory domain:(NSString *)baseDomain; 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /feature.md: -------------------------------------------------------------------------------- 1 | 2 | ### AppHost 的功能总览 3 | ![功能总览](https://upload-images.jianshu.io/upload_images/277783-2957bbc40a8287c9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 4 | 5 | 分两部分,AppHost Core 为 native 开发提供基础模块和基本功能封装;AppHost Debug Service 为 native、H5 前端、QA 等人员提供调试服务。下面详细介绍功能。 6 | ### AppHostCore 7 | 1. **webview 核心,包含常见需求的实现逻辑**,包括 8 | - native 和 H5 的通讯协议,封装了 native 端解析、H5 端发送逻辑。native 开发人员只需要面向业务编码,然后通知 H5; H5 开发面向业务开发,只需关心两个接口。 9 | - 新增接口用继承 AppHostResponse 的方式实现,解决业务需求增长、代码无序膨胀的问题。特别的,支持业务接口的延迟加载,不使用的接口,不会初始化。 10 | - Cookie管理,最烦人的 Cookie 丢失和 Cookie 同步问题,已经内部妥善处理 11 | - 一些人性化的小优化。更合理的进度条、浏览历史滚动记忆、增强的 native 执行 js 代码能力、基本的 API 接口调用鉴权等等。 12 | 2. **JSBridge 接口管理** 13 | - 独立于 App 的版本号,H5 使用特性嗅探实现新旧 App 的兼容,简单直观。 14 | - API 接口签名,可实现 API 参数粒度的接口升级和开关管理。 15 | 3. **资源加载** 16 | - 加载远程 URL,单向同步 Cookie 到 WKWebView。 17 | - 本地文件夹资源,可实现用离线包渲染动态页面。 18 | - 某些业务场景下,可实现 HTML 里的 xhr、js、css 资源请求拦截,不需要动用私有 webkit API。 19 | 4. **API 接口文档一体化和 testcase 自动化** 20 | 对于“native 为 H5 提供接口” 这件事情,如前述,需要多方的协调同步,很容易出现:接口文档过时、文档缺失、接口查找麻烦、接入新 API 不直观、测试不方便,QA 回归不充分,或者是多个环节重复写测试用例等坏情况。 21 | ***AppHost 的 API 文档模块,将这些环节需要的文档和测试用例,全部集中到开发阶段完成,后续 H5 查询的 API 文档、QA 走查用例、自动化测试,全部自动生成***。保证接口文档的最新,省去多个环节的重复建设,内置的自动化测试支持,方便 QA 使用脚本回归测试。 22 | ### AppHost Debug Service 23 | 1. **Remote Debugger 通过电脑浏览器提供 Debug Service** 24 | 电脑浏览器具有访问方便、可展示区域大、输入快捷方便、易于集成第三方调试工具的特点。相同的调试功能,理论上也可以集成在手机 App端,但是体验会大打折扣,是个低效的调试方案。 25 | 2. **帮助系统和文档**。 26 | 在浏览器端 Console ,实现了一个小型命令行程序,指导用户如何使用本 Remote Debugger;同时还提供 **即时查询 API 接口** 名称、参数解释、示例代码等功能,让你的工作流不需要切换到打开的API 文档文件或者浏览器,保持操作上下文。 27 | 3. **REPL[(Read-Eval-Print Loop)](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)环境** 28 | Console 里实现了完整 webview 执行环境,将传统语言的探索、调试新特性的体验带到 Remote Debugger,如同 Bash 的` shell prompt`和 ruby 的`irb `那样。在这里输入的所有命令如同在远端 webview 的 console 里执行一样。当然还有`Off mobile` 和`On mobile `来切换当前命令是本地执行还是远端 webview 执行。 29 | 4. **辅助功能** 30 | Console 提供了左侧快捷命令;内置了命令的历史记忆,实现上下箭头遍历;支持` :clear `,清除当前界面等功能 31 | 5. **扩展性** 32 | - 和 Safari 的 Develop 工具配合 33 | 我们知道 Safari Develop 工具是在页面打开后才会出现。如果我们有个页面由 302 跳转,我们想抓到想要的请求是做不到的。接入 AppHost 之后,我们保持Safari Develop 工具打开的状态,在 Remote Debugger 输入命令 ,让当前 webview 加载初始 URL,这样我们就可以抓到从 302 跳转开始后的网络请求了。 34 | - 集成 weinre。 35 | 通过`:weinre --` 命令,不需要改动被调试页面的源码,即可提供 weinre 调试服务,而且一次注入当前 App 启动后全程有效,后续页面无需再注入。用这个特性,甚至可以调试第三方页面。 36 | - 支持 console.log。这个无需赘言,曾经的` [https://jsconsole.com/](https://jsconsole.com/) 是首选的远程调试服务。AppHost 内置此功能。 37 | 6. **演示** 38 | 39 | 6.1 **基本操作演示** 40 | ![Debugger 整体使用](https://upload-images.jianshu.io/upload_images/277783-e520ecf4d92e53da.gif?imageMogr2/auto-orient/strip) 41 | 42 | 6.2 **查看严选首页的 onload 事件时间** 43 | ![查看当前 H5 页面的 timing 数据](https://upload-images.jianshu.io/upload_images/277783-7b99adf129b64dc1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 44 | 45 | 6.3 **使用 weinre 调试严选页面** 46 | ![weinre服务](https://upload-images.jianshu.io/upload_images/277783-d7113e5153fc074b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 47 | 48 | -------------------------------------------------------------------------------- /AppHost/Boost/Preloader/AHWebViewPreLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // AHWebViewPreLoader.m 3 | // 4 | // 5 | // Created by liang on 2019/7/16. 6 | // Copyright © 2019 Smily.Co. All rights reserved. 7 | // 8 | 9 | #import "AHWebViewPreLoader.h" 10 | #import "AHSimpleWebViewController.h" 11 | 12 | 13 | NSString * const kPreloadResourceConfCacheKey = @"kPreloadResourceConfCacheKey"; 14 | 15 | @implementation AHWebViewPreLoader { 16 | UIWindow *_no3; 17 | } 18 | 19 | + (instancetype)defaultLoader { 20 | static AHWebViewPreLoader *kDefaultLoader = nil; 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | kDefaultLoader = [AHWebViewPreLoader new]; 24 | 25 | UIWindow *win = [[UIWindow alloc] initWithFrame:CGRectMake(AH_SCREEN_WIDTH, 0, 10, 10)]; 26 | 27 | win.windowLevel = UIWindowLevelStatusBar + 14; 28 | 29 | kDefaultLoader->_no3 = win; 30 | }); 31 | 32 | return kDefaultLoader; 33 | } 34 | 35 | /** 36 | 从服务器下载最新的配置到 ud 里 37 | */ 38 | - (void)updateConfig:(bFetchConfig)fetchConfig { 39 | if (fetchConfig) { 40 | NSDictionary *conf = fetchConfig(kAHPreloadResourceVersion); 41 | if ([conf isKindOfClass:NSDictionary.class]) { 42 | [[NSUserDefaults standardUserDefaults] setObject:conf forKey:kPreloadResourceConfCacheKey]; 43 | } 44 | 45 | } 46 | AHLog("kPreloadResourceVersion = %d\n", kAHPreloadResourceVersion); 47 | } 48 | 49 | - (void)loadResources { 50 | // 使用上次获取的数据 51 | NSDictionary *conf = [[NSUserDefaults standardUserDefaults] objectForKey:kPreloadResourceConfCacheKey]; 52 | 53 | if ([conf isKindOfClass:NSDictionary.class]) { 54 | [self renderWebView:conf]; 55 | } 56 | } 57 | 58 | -(NSString*)jsonStringFromDictionary:(NSDictionary *)dic { 59 | NSError *error; 60 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic 61 | options:0 62 | error:&error]; 63 | 64 | if (!jsonData) { 65 | AHLog(@"%s: error: %@", __func__, error.localizedDescription); 66 | return @"{}"; 67 | } else { 68 | return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 69 | } 70 | } 71 | 72 | - (void)renderWebView:(NSDictionary *)model { 73 | // 获取模板字符串,准备替换 #{CONF} 74 | NSURL *resourceURL = [[NSBundle mainBundle] URLForResource:@"preload_resources" withExtension:@"html"]; 75 | NSError *err = nil; 76 | NSString *tmpl = [NSString stringWithContentsOfURL:resourceURL encoding:NSUTF8StringEncoding error:&err]; 77 | NSString *dataSource = [self jsonStringFromDictionary: model]; 78 | 79 | AHSimpleWebViewController *vc = [AHSimpleWebViewController new]; 80 | vc.domain = [model objectForKey:@"domain"]; 81 | vc.htmlString = [tmpl stringByReplacingOccurrencesOfString:@"#{CONF}" withString:dataSource];; 82 | 83 | _no3.rootViewController = vc; 84 | _no3.hidden = NO;// 必须是显示的 window,才会触发预热 ViewController,隐藏的 window 不可用。但是和是否在屏幕可见没关系 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /AppHost/RemoteDebug/components/tool-panel.js: -------------------------------------------------------------------------------- 1 | window.ah_env = { 2 | // 表示当前命令行执行的环境,isMobile = true 表示所有的语句是在远端的 webview 里执行的; false 表示是当前浏览器 3 | isMobile: false 4 | }; 5 | 6 | Vue.component("tool-panel", { 7 | data: function () { 8 | return { 9 | dataSource: [ 10 | { 11 | action: "switch", 12 | clsName: "w-tool-item w-tool-switch", 13 | title: "点击进入 mobile", 14 | text: "Off Mobile" 15 | }, 16 | { 17 | action: "help", 18 | clsName: "w-tool-item w-tool-help", 19 | title: "look for help", 20 | text: "Help" 21 | }, 22 | { 23 | action: "list", 24 | clsName: "w-tool-item w-tool-docs", 25 | title: "documents for api", 26 | text: "Docs" 27 | }, 28 | { 29 | action: "timing", 30 | clsName: "w-tool-item w-tool-timing", 31 | title: "Audit", 32 | text: "Timing" 33 | } 34 | ] 35 | }; 36 | }, 37 | methods: { 38 | useTool: function (e) { 39 | var _real_switch_env = function(){ 40 | Vue.set(this.dataSource, 0, { 41 | action: "switch", 42 | clsName: "w-tool-item w-tool-switch", 43 | title: window.ah_env.isMobile ? "点击退出 mobile" : "点击进入 mobile", 44 | text: window.ah_env.isMobile ? "On Mobile" : "Off Mobile" 45 | }); 46 | // 使用原生的方法操作 ele,切换 输入栏处的图标 47 | document.getElementsByClassName('j-mobile2pc')[0].style = window.ah_env.isMobile ? 'display:block' : 'display:none'; 48 | document.getElementsByClassName('j-pc2mobile')[0].style = window.ah_env.isMobile ? 'display:none' : 'display:block'; 49 | var runBtn = document.getElementById('command'); 50 | if (window.ah_env.isMobile) { 51 | runBtn.placeholder = '在这里输入脚本,点击 Run 执行'; 52 | } else { 53 | runBtn.placeholder = '输入命令,如. :help'; 54 | } 55 | }.bind(this); 56 | 57 | var ele = e.target; 58 | var action = ele.dataset.action; 59 | switch (action) { 60 | case "switch": 61 | { 62 | window.ah_env.isMobile = !window.ah_env.isMobile; 63 | _real_switch_env(); 64 | } 65 | break; 66 | case 'help': 67 | case 'list': 68 | case 'timing': 69 | { 70 | window.ah_env.isMobile = false; 71 | _real_switch_env(); 72 | _run_command(':' + action); 73 | } 74 | break; 75 | } 76 | } 77 | }, 78 | template: "#tool-panel-template" 79 | }); -------------------------------------------------------------------------------- /AppHost/ThirdParty/Objective-C-HMTL-Parser/HTMLParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // HTMLParser.m 3 | // StackOverflow 4 | // 5 | // Created by Ben Reeves on 09/03/2010. 6 | // Copyright 2010 Ben Reeves. All rights reserved. 7 | // 8 | 9 | #import "HTMLParser.h" 10 | 11 | 12 | @implementation HTMLParser 13 | 14 | -(HTMLNode*)doc 15 | { 16 | if (_doc == NULL) 17 | return NULL; 18 | 19 | return [[HTMLNode alloc] initWithXMLNode:(xmlNode*)_doc]; 20 | } 21 | 22 | -(HTMLNode*)html 23 | { 24 | if (_doc == NULL) 25 | return NULL; 26 | 27 | return [[self doc] findChildTag:@"html"]; 28 | } 29 | 30 | -(HTMLNode*)head 31 | { 32 | if (_doc == NULL) 33 | return NULL; 34 | 35 | return [[self doc] findChildTag:@"head"]; 36 | } 37 | 38 | -(HTMLNode*)body 39 | { 40 | if (_doc == NULL) 41 | return NULL; 42 | 43 | return [[self doc] findChildTag:@"body"]; 44 | } 45 | 46 | -(id)initWithString:(NSString*)string error:(NSError**)error 47 | { 48 | if (self = [super init]) 49 | { 50 | _doc = NULL; 51 | 52 | if ([string length] > 0) 53 | { 54 | CFStringEncoding cfenc = CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding); 55 | CFStringRef cfencstr = CFStringConvertEncodingToIANACharSetName(cfenc); 56 | const char *enc = CFStringGetCStringPtr(cfencstr, 0); 57 | // _doc = htmlParseDoc((xmlChar*)[string UTF8String], enc); 58 | int optionsHtml = HTML_PARSE_RECOVER; 59 | optionsHtml = optionsHtml | HTML_PARSE_NOERROR; //Uncomment this to see HTML errors 60 | optionsHtml = optionsHtml | HTML_PARSE_NOWARNING; 61 | _doc = htmlReadDoc ((xmlChar*)[string UTF8String], NULL, enc, optionsHtml); 62 | } 63 | else 64 | { 65 | if (error) { 66 | *error = [NSError errorWithDomain:@"HTMLParserdomain" code:1 userInfo:nil]; 67 | } 68 | } 69 | } 70 | 71 | return self; 72 | } 73 | 74 | -(id)initWithData:(NSData*)data error:(NSError**)error 75 | { 76 | if (self = [super init]) 77 | { 78 | _doc = NULL; 79 | 80 | if (data) 81 | { 82 | CFStringEncoding cfenc = CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding); 83 | CFStringRef cfencstr = CFStringConvertEncodingToIANACharSetName(cfenc); 84 | const char *enc = CFStringGetCStringPtr(cfencstr, 0); 85 | //_doc = htmlParseDoc((xmlChar*)[data bytes], enc); 86 | 87 | _doc = htmlReadDoc((xmlChar*)[data bytes], 88 | "", 89 | enc, 90 | XML_PARSE_NOERROR | XML_PARSE_NOWARNING); 91 | } 92 | else 93 | { 94 | if (error) 95 | { 96 | *error = [NSError errorWithDomain:@"HTMLParserdomain" code:1 userInfo:nil]; 97 | } 98 | 99 | } 100 | } 101 | 102 | return self; 103 | } 104 | 105 | -(id)initWithContentsOfURL:(NSURL*)url error:(NSError**)error 106 | { 107 | 108 | NSData * _data = [[NSData alloc] initWithContentsOfURL:url options:0 error:error]; 109 | 110 | if (_data == nil || *error) 111 | { 112 | return nil; 113 | } 114 | 115 | self = [self initWithData:_data error:error]; 116 | 117 | return self; 118 | } 119 | 120 | 121 | -(void)dealloc 122 | { 123 | if (_doc) 124 | { 125 | xmlFreeDoc(_doc); 126 | } 127 | 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /AppHost/resources/pushstate_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 60 | 第三方接口 61 | 62 | 63 | 64 |
65 | push state 接口道接口测试 66 |
67 |
68 | 测试webview浏览器 69 | 70 |
    71 |
  1. 72 | 73 | 点击之后,url改变,上面有个display部分会有page=1。 74 |
  2. 75 |
76 |
77 |
78 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Progressor.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Progressor.m 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/23. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AppHostViewController+Progressor.h" 10 | #import 11 | 12 | @implementation AppHostViewController (Progressor) 13 | 14 | - (void)startProgressor 15 | { 16 | [self stopProgressor]; 17 | if (self.progressorView == nil) { 18 | [self addWebviewProgressor]; 19 | } 20 | } 21 | 22 | - (void)addWebviewProgressor 23 | { 24 | // 仿微信进度条 25 | self.progressorView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, AH_NAVIGATION_BAR_HEIGHT, AH_SCREEN_WIDTH, 20.0f)]; 26 | 27 | self.progressorView.progressTintColor = kWebViewProgressTintColorRGB > 0? AHColorFromRGB(kWebViewProgressTintColorRGB):[UIColor grayColor]; 28 | self.progressorView.trackTintColor = [UIColor whiteColor]; 29 | [self.view addSubview:self.progressorView]; 30 | } 31 | 32 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 33 | { 34 | if ([keyPath isEqualToString:@"estimatedProgress"]) { 35 | double progress = [change[@"new"] doubleValue]; 36 | AHLog(@"[Timing] progress = %f, %f", progress, [[NSDate date] timeIntervalSince1970] * 1000); 37 | if (progress >= 1) { 38 | // 0.25s 后消失 39 | [CATransaction begin]; 40 | [CATransaction setCompletionBlock:^{ 41 | self.clearProgressorTimer = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(stopProgressor) userInfo:nil repeats:YES]; 42 | [[NSRunLoop currentRunLoop] addTimer:self.clearProgressorTimer forMode:NSRunLoopCommonModes]; 43 | }]; 44 | [self.progressorView setProgress:1 animated:YES]; 45 | [CATransaction commit]; 46 | } else { 47 | [self.progressorView setProgress:progress animated:YES]; 48 | } 49 | } else { 50 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 51 | } 52 | } 53 | 54 | - (void)stopProgressor 55 | { 56 | [self.progressorView setProgress:0]; 57 | [self.clearProgressorTimer invalidate]; 58 | } 59 | 60 | - (void)setupProgressor 61 | { 62 | [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil]; 63 | } 64 | 65 | - (void)teardownProgressor 66 | { 67 | [self.webView removeObserver:self forKeyPath:@"estimatedProgress"]; 68 | } 69 | 70 | #pragma mark - setter, setter 71 | 72 | - (NSTimer *)clearProgressorTimer 73 | { 74 | return objc_getAssociatedObject(self, @selector(clearProgressorTimer)); 75 | } 76 | 77 | - (void)setClearProgressorTimer:(NSTimer *)clearProgressorTimer 78 | { 79 | objc_setAssociatedObject(self, @selector(clearProgressorTimer), clearProgressorTimer, OBJC_ASSOCIATION_RETAIN); 80 | } 81 | 82 | - (UIProgressView *)progressorView 83 | { 84 | return objc_getAssociatedObject(self, @selector(progressorView)); 85 | } 86 | 87 | - (void)setProgressorView:(UIProgressView *)progressorView 88 | { 89 | objc_setAssociatedObject(self, @selector(progressorView), progressorView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/Objective-C-HMTL-Parser/HTMLNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // HTMLNode.h 3 | // StackOverflow 4 | // 5 | // Created by Ben Reeves on 09/03/2010. 6 | // Copyright 2010 Ben Reeves. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "HTMLParser.h" 12 | 13 | @class HTMLParser; 14 | 15 | #define ParsingDepthUnlimited 0 16 | #define ParsingDepthSame -1 17 | #define ParsingDepth size_t 18 | 19 | typedef enum 20 | { 21 | HTMLHrefNode, 22 | HTMLTextNode, 23 | HTMLUnkownNode, 24 | HTMLCodeNode, 25 | HTMLSpanNode, 26 | HTMLPNode, 27 | HTMLLiNode, 28 | HTMLUlNode, 29 | HTMLImageNode, 30 | HTMLOlNode, 31 | HTMLStrongNode, 32 | HTMLPreNode, 33 | HTMLBlockQuoteNode, 34 | } HTMLNodeType; 35 | 36 | @interface HTMLNode : NSObject 37 | { 38 | @public 39 | xmlNode * _node; 40 | } 41 | 42 | //Init with a lib xml node (shouldn't need to be called manually) 43 | //Use [parser doc] to get the root Node 44 | -(id)initWithXMLNode:(xmlNode*)xmlNode; 45 | 46 | //Returns a single child of class 47 | -(HTMLNode*)findChildOfClass:(NSString*)className; 48 | 49 | //Returns all children of class 50 | -(NSArray*)findChildrenOfClass:(NSString*)className; 51 | 52 | //Finds a single child with a matching attribute 53 | //set allowPartial to match partial matches 54 | //e.g. "]; 75 | return description; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /AppHost/Core/html/eval.js: -------------------------------------------------------------------------------- 1 | // 本文件为了替换 self.webView evaluateJavaScript:javaScriptString completionHandler:nil 2 | 3 | !(function (window) { 4 | // https://stackoverflow.com/questions/7893776/the-most-accurate-way-to-check-js-objects-type 5 | window.ah_typeof = function (global) { 6 | var cache = {}; 7 | return function (obj) { 8 | var key; 9 | return obj === null? "null" // null 10 | : obj === global? "global" // window in browser or global in nodejs 11 | : (key = typeof obj) !== "object"? key // basic: string, boolean, number, undefined, function 12 | : obj.nodeType? "DOMElement" // DOM element 13 | : cache[(key = {}.toString.call(obj))] || // cached. date, regexp, error, object, array, math 14 | (cache[key] = key.slice(8, -1).toLowerCase()); // get XXXX from [object XXXX], and cache it 15 | }; 16 | }(window); 17 | // 18 | var safeCopy = function(_origin){ 19 | if (!_origin) return; 20 | var r = {}; 21 | for (var key in _origin) { 22 | if (_origin.hasOwnProperty(key)) { 23 | var val = _origin[key]; 24 | if(val == null) break; 25 | 26 | var objType = window.ah_typeof(val); 27 | if (['object','array'].indexOf(objType) > -1){ 28 | r[key] = Object.prototype.toString.call(val); 29 | } else if('string, boolean, number,date'.indexOf(objType) > -1) { 30 | r[key] = _origin[key]; 31 | } 32 | } 33 | } 34 | return r; 35 | }; 36 | 37 | var serialize = function(obj){ 38 | var r = null; 39 | switch(ah_typeof(obj)){ 40 | case 'null': 41 | case 'global': 42 | case 'string': 43 | case 'boolean': 44 | case 'number': 45 | case 'undefined': 46 | case 'date': 47 | case 'array': 48 | // 以上不处理 49 | r = obj; 50 | break; 51 | case 'location': 52 | case 'window': 53 | r = safeCopy(obj); 54 | break; 55 | case 'regexp': 56 | r = Object.toString.call(obj); 57 | break; 58 | case 'DOMElement': 59 | r = { 60 | nodeType: obj.nodeType, 61 | nodeName: obj.nodeName, 62 | html: obj.outerHTML.length > 100 ? obj.outerHTML.substring(0,100): obj.outerHTML 63 | }; 64 | break; 65 | default: 66 | if(typeof obj.toJSON === 'function'){ 67 | r = obj.toJSON(); 68 | } else { 69 | r = 'Unsupported Type: ' + obj; 70 | } 71 | 72 | } 73 | return r; 74 | }; 75 | // https://weblogs.asp.net/yuanjian/json-performance-comparison-of-eval-new-function-and-json 76 | window.ah_eval = function (r) { 77 | var err = null; 78 | 79 | r = serialize(r); 80 | if (err == null && r == null){ 81 | return {}; 82 | } else if (err == null && r != null){ 83 | return {'result': r}; 84 | } else if (err != null && r == null){ 85 | return {'err': err}; 86 | } else { 87 | return {'result': r, 'err': err}; 88 | } 89 | }; 90 | })(this); 91 | -------------------------------------------------------------------------------- /AppHost/Core/AHSchemeTaskDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AHSchemeTaskResponse.m 3 | // AppHost 4 | // 5 | // Created by liang on 2018/12/29. 6 | // Copyright © 2018 liang. All rights reserved. 7 | // 8 | 9 | #import "AHSchemeTaskDelegate.h" 10 | #import "AppHostProtocol.h" 11 | 12 | @interface AHSchemeTaskDelegate() 13 | 14 | /** 15 | 保存自定义的 16 | handles 17 | */ 18 | @property (nonatomic, strong) NSMutableDictionary *customHandles; 19 | 20 | @end 21 | 22 | @implementation AHSchemeTaskDelegate 23 | 24 | - (instancetype)init 25 | { 26 | if (self = [super init]) { 27 | self.customHandles = [NSMutableDictionary dictionaryWithCapacity:4]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)dealloc 33 | { 34 | AHLog(@"AHSchemeTaskDelegate dealloc"); 35 | } 36 | 37 | - (void)addHandler:(bSchemeTaskHandler)handler forDomain:(NSString */* js */)domain; 38 | { 39 | if (domain.length == 0 || handler == nil) { 40 | AHLog(@"domain or handle is null"); 41 | return; 42 | } 43 | 44 | [self.customHandles setObject:handler forKey:domain]; 45 | } 46 | 47 | #pragma mark - url task 48 | 49 | - (void)webView:(WKWebView *)webView startURLSchemeTask:(nonnull id)urlSchemeTask 50 | { 51 | NSURLRequest *request = urlSchemeTask.request; 52 | NSString *path = [request.URL path]; 53 | AHLog(@"URL = %@, allKey = %@", request.URL, [request.allHTTPHeaderFields allKeys]); 54 | NSData *data; 55 | NSString *host = [request.URL host]; 56 | 57 | if (host.length == 0) { 58 | return; 59 | } 60 | 61 | NSString *mime = nil; 62 | bSchemeTaskHandler handle = [self.customHandles objectForKey:host]; 63 | if (handle) { 64 | data = handle(webView, urlSchemeTask, &mime); 65 | } 66 | 67 | // 上面没有处理,使用默认逻辑 68 | if (data == nil) { 69 | if ([host isEqualToString:kAppHostURLScriptHost]) { 70 | NSURL *url = [NSURL fileURLWithPath:path]; 71 | data = [NSData dataWithContentsOfURL:url]; 72 | mime = @"application/javascript"; 73 | if (!data) { 74 | AHLog(@"Read script file error. The path is %@", url); 75 | } 76 | } else if ([host isEqualToString:kAppHostURLStyleHost]) { 77 | NSURL *url = [NSURL fileURLWithPath:path]; 78 | data = [NSData dataWithContentsOfURL:url]; 79 | mime = @"text/css"; 80 | if (!data) { 81 | AHLog(@"Read style file error. The path is %@", url); 82 | } 83 | } else if ([host isEqualToString:kAppHostURLImageHost]) { 84 | NSURL *imageURL = [NSURL fileURLWithPath:path]; 85 | data = [NSData dataWithContentsOfURL:imageURL]; 86 | mime = @"image/png"; 87 | if (!data) { 88 | AHLog(@"Read image file error. The path is %@", imageURL); 89 | } 90 | } 91 | } 92 | 93 | if (data == nil) { 94 | NSError *err = [[NSError alloc] initWithDomain:@"自定义的资源无法解析" code:-4003 userInfo:nil]; 95 | [urlSchemeTask didFailWithError:err]; 96 | } else { 97 | NSURLResponse *response = [[NSURLResponse alloc] initWithURL:urlSchemeTask.request.URL MIMEType:mime?:@"text/plain" expectedContentLength:data.length textEncodingName:nil]; 98 | [urlSchemeTask didReceiveResponse:response]; 99 | [urlSchemeTask didReceiveData:data]; 100 | [urlSchemeTask didFinish]; 101 | } 102 | } 103 | 104 | - (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask { 105 | // 106 | AHLog(@"%@", NSStringFromSelector(_cmd)); 107 | } 108 | 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Responses/GCDWebServerStreamedResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerResponse.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerStreamBlock is called to stream the data for the HTTP body. 34 | * The block must return either a chunk of data, an empty NSData when done, or 35 | * nil on error and set the "error" argument which is guaranteed to be non-NULL. 36 | */ 37 | typedef NSData* _Nullable (^GCDWebServerStreamBlock)(NSError** error); 38 | 39 | /** 40 | * The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock 41 | * except the streamed data can be returned at a later time allowing for 42 | * truly asynchronous generation of the data. 43 | * 44 | * The block must call "completionBlock" passing the new chunk of data when ready, 45 | * an empty NSData when done, or nil on error and pass a NSError. 46 | * 47 | * The block cannot call "completionBlock" more than once per invocation. 48 | */ 49 | typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock); 50 | 51 | /** 52 | * The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams 53 | * the body of the HTTP response using a GCD block. 54 | */ 55 | @interface GCDWebServerStreamedResponse : GCDWebServerResponse 56 | @property(nonatomic, copy) NSString* contentType; // Redeclare as non-null 57 | 58 | /** 59 | * Creates a response with streamed data and a given content type. 60 | */ 61 | + (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; 62 | 63 | /** 64 | * Creates a response with async streamed data and a given content type. 65 | */ 66 | + (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; 67 | 68 | /** 69 | * Initializes a response with streamed data and a given content type. 70 | */ 71 | - (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; 72 | 73 | /** 74 | * This method is the designated initializer for the class. 75 | */ 76 | - (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; 77 | 78 | @end 79 | 80 | NS_ASSUME_NONNULL_END 81 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/Reachability/Reachability.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Tony Million. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | #import 30 | 31 | 32 | /** 33 | * Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X. 34 | * 35 | * @see http://nshipster.com/ns_enum-ns_options/ 36 | **/ 37 | #ifndef NS_ENUM 38 | #define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type 39 | #endif 40 | 41 | extern NSString *const kReachabilityChangedNotification; 42 | 43 | typedef NS_ENUM(NSInteger, NetworkStatus) { 44 | // Apple NetworkStatus Compatible Names. 45 | NotReachable = 0, 46 | ReachableViaWiFi = 2, 47 | ReachableViaWWAN = 1 48 | }; 49 | 50 | @class Reachability; 51 | 52 | typedef void (^NetworkReachable)(Reachability * reachability); 53 | typedef void (^NetworkUnreachable)(Reachability * reachability); 54 | 55 | 56 | @interface Reachability : NSObject 57 | 58 | @property (nonatomic, copy) NetworkReachable reachableBlock; 59 | @property (nonatomic, copy) NetworkUnreachable unreachableBlock; 60 | 61 | @property (nonatomic, assign) BOOL reachableOnWWAN; 62 | 63 | 64 | +(Reachability*)reachabilityWithHostname:(NSString*)hostname; 65 | // This is identical to the function above, but is here to maintain 66 | //compatibility with Apples original code. (see .m) 67 | +(Reachability*)reachabilityWithHostName:(NSString*)hostname; 68 | +(Reachability*)reachabilityForInternetConnection; 69 | +(Reachability*)reachabilityWithAddress:(void *)hostAddress; 70 | +(Reachability*)reachabilityForLocalWiFi; 71 | 72 | -(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; 73 | 74 | -(BOOL)startNotifier; 75 | -(void)stopNotifier; 76 | 77 | -(BOOL)isReachable; 78 | -(BOOL)isReachableViaWWAN; 79 | -(BOOL)isReachableViaWiFi; 80 | 81 | // WWAN may be available, but not active until a connection has been established. 82 | // WiFi may require a connection for VPN on Demand. 83 | -(BOOL)isConnectionRequired; // Identical DDG variant. 84 | -(BOOL)connectionRequired; // Apple's routine. 85 | // Dynamic, on demand connection? 86 | -(BOOL)isConnectionOnDemand; 87 | // Is user intervention required? 88 | -(BOOL)isInterventionRequired; 89 | 90 | -(NetworkStatus)currentReachabilityStatus; 91 | -(SCNetworkReachabilityFlags)reachabilityFlags; 92 | -(NSString*)currentReachabilityString; 93 | -(NSString*)currentReachabilityFlags; 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /AppHost/Boost/Prefetch/AHPrefetchMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // AHPrefetchMonitor.m 3 | // AppHost 4 | // 5 | // Created by hite on 2021/3/12. 6 | // Copyright © 2021 liang. All rights reserved. 7 | // 8 | 9 | #import "AHPrefetchMonitor.h" 10 | 11 | @implementation AHPrefetchMetrics 12 | 13 | @end 14 | 15 | @implementation AHPrefetchMonitor{ 16 | // 线程安全 17 | NSCache *_metricsCache; 18 | id _log; 19 | } 20 | 21 | + (instancetype)sharedInstance { 22 | static dispatch_once_t once; 23 | static AHPrefetchMonitor *instance; 24 | dispatch_once(&once, ^{ 25 | instance = [self new]; 26 | instance->_metricsCache = [[NSCache alloc] init]; 27 | }); 28 | return instance; 29 | } 30 | 31 | - (void)setLogger:(id)logger{ 32 | _log = logger; 33 | } 34 | 35 | - (void)markLoadTime:(NSInteger)realTime forHash:(int32_t)hash url:(NSString *)url api:(NSString *)api{ 36 | NSNumber *key = @(hash); 37 | [self->_metricsCache removeObjectForKey:key]; 38 | 39 | AHPrefetchMetrics *metric = [AHPrefetchMetrics new]; 40 | metric.hashKey = hash; 41 | metric.url = url; 42 | metric.api = api; 43 | if (realTime > 0) { 44 | metric.loadTime = realTime; 45 | } else { 46 | metric.loadTime = [[NSDate date] timeIntervalSince1970] * 1000; 47 | } 48 | 49 | [self->_metricsCache setObject:metric forKey:key]; 50 | } 51 | 52 | - (void)markReadyTime:(int32_t)hash{ 53 | AHPrefetchMetrics *metric = [self->_metricsCache objectForKey:@(hash)]; 54 | if (metric) { 55 | metric.readyTime = [[NSDate date] timeIntervalSince1970] * 1000; 56 | } 57 | } 58 | 59 | - (void)markLoadFailTime:(int32_t)hash{ 60 | AHPrefetchMetrics *metric = [self->_metricsCache objectForKey:@(hash)]; 61 | if (metric) { 62 | metric.readyTime = [[NSDate date] timeIntervalSince1970] * 1000; 63 | } 64 | } 65 | 66 | - (AHPrefetchMetrics *)metricForHash:(int32_t)hash{ 67 | return [self->_metricsCache objectForKey:@(hash)];; 68 | } 69 | 70 | - (void)markFetchTime:(int32_t)hash{ 71 | AHPrefetchMetrics *metric = [self->_metricsCache objectForKey:@(hash)]; 72 | AHPrefetchStatus status = AHPrefetchStatusDefault; 73 | if (metric) { 74 | metric.fetchTime = [[NSDate date] timeIntervalSince1970] * 1000; 75 | 76 | if (metric.loadFailTime > 0){ 77 | status = AHPrefetchStatusFailed; 78 | } else if (metric.readyTime > 0){ 79 | status = AHPrefetchStatusSucc; 80 | } else if (metric.readyTime == 0){ 81 | status = AHPrefetchStatusInProcessing; 82 | } 83 | } else { 84 | status = AHPrefetchStatusUnHit; 85 | } 86 | // 发送统计埋点 87 | [_log logAction:@"h5_prefetch_status" 88 | tags:@{ 89 | @"status":[NSString stringWithFormat:@"status_%@", @(status)], 90 | } fields:@{ 91 | @"url": metric.url?:@"", 92 | @"api": metric.api?:@"", 93 | @"hashFromH5": @(hash), // 用来排查错误 94 | @"loadCostTime": @(metric.readyTime - metric.loadTime), 95 | @"ready2fetchTime": @(metric.fetchTime - metric.readyTime) 96 | }]; 97 | [self->_metricsCache removeObjectForKey:@(hash)]; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /AppHost/Core/URLChecker/AHURLChecker.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKURLChecker.m 3 | 4 | // Created by hite on 4/22/16. 5 | // Copyright © 2016 smilly.co. All rights reserved. 6 | // 7 | 8 | #import "AHURLChecker.h" 9 | #import "AHAppWhiteListParser.h" 10 | 11 | @implementation AHURLChecker 12 | 13 | static AHURLChecker *_sharedManager = nil; 14 | static NSDictionary *_authorizedTable = nil; 15 | + (instancetype)sharedManager 16 | { 17 | 18 | static dispatch_once_t onceToken; 19 | 20 | dispatch_once(&onceToken, ^{ 21 | _sharedManager = [[self alloc] init]; 22 | // 默认数据 23 | NSString *path = [[NSBundle mainBundle] pathForResource:@"app-access" ofType:@"txt"]; 24 | NSString *fileContents = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; 25 | _authorizedTable = [[AHAppWhiteListParser sharedManager] parserFileContent:fileContents]; 26 | }); 27 | 28 | return _sharedManager; 29 | } 30 | 31 | - (BOOL)checkURL:(NSURL *)url forAuthorizationType:(AHAuthorizationType)authType 32 | { 33 | #ifdef DEBUG 34 | return YES; 35 | #else 36 | if (url == nil) { 37 | return NO; 38 | } 39 | // 本地测试地址。 40 | NSString *directory = NSHomeDirectory(); // user文件根目录 /var/mobile/.. 41 | NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; // /var/containers/ 42 | NSString *bundleURL = [[[NSBundle mainBundle] bundleURL] absoluteString]; // file:/// 43 | NSString *tempDir = NSTemporaryDirectory(); //在真机上/private/.. 44 | if ([url.absoluteString hasPrefix:directory] || [url.absoluteString hasPrefix:bundlePath] || [url.absoluteString hasPrefix:bundleURL] || 45 | [url.absoluteString hasPrefix:tempDir]) { 46 | return YES; 47 | } 48 | 49 | NSString *key = nil; 50 | if (authType == AHAuthorizationTypeSchema) { 51 | key = @"schema-open-url"; 52 | } 53 | else if (authType == AHAuthorizationTypeAppHost) { 54 | key = @"apphost"; 55 | } 56 | if (key) { 57 | NSArray *rules = [_authorizedTable objectForKey:key]; 58 | if ([rules count] == 0) { 59 | return YES; // 白名单为空,放行 60 | } 61 | BOOL pass = NO; 62 | 63 | for (NSInteger i = 0, l = [rules count]; i < l; i++) { 64 | NSString *rule = [rules objectAtIndex:i]; 65 | // 将.号处理为\.如mail.163.com => mail\\.163\\.com; 66 | rule = [rule stringByReplacingOccurrencesOfString:@"." withString:@"\\."]; 67 | // 将*号处理为 [a-z0-9]+, 68 | rule = [rule stringByReplacingOccurrencesOfString:@"*" withString:@"[\\w\\d-_]+"]; 69 | // 精确匹配. 开始和结尾 70 | rule = [NSString stringWithFormat:@"^%@$", rule]; 71 | 72 | NSError *regexError = nil; 73 | NSRegularExpression *regex = [NSRegularExpression 74 | regularExpressionWithPattern:rule 75 | options:NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators 76 | error:®exError]; 77 | 78 | if (regexError) { 79 | NSLog(@"Regex creation failed with error: %@", [regexError description]); 80 | continue; 81 | } 82 | // 使用host 83 | NSString *host = [url host]; 84 | NSArray *matches = [regex matchesInString:host options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, host.length)]; 85 | if ([matches count] > 0) { 86 | pass = YES; 87 | break; 88 | } 89 | } 90 | return pass; 91 | } 92 | else { 93 | return YES; // 不在授权类型里的,默认返回通过。 94 | } 95 | #endif 96 | } 97 | 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Requests/GCDWebServerDataRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "GCDWebServerPrivate.h" 33 | 34 | @interface GCDWebServerDataRequest () 35 | @property(nonatomic) NSMutableData* data; 36 | @end 37 | 38 | @implementation GCDWebServerDataRequest { 39 | NSString* _text; 40 | id _jsonObject; 41 | } 42 | 43 | - (BOOL)open:(NSError**)error { 44 | if (self.contentLength != NSUIntegerMax) { 45 | _data = [[NSMutableData alloc] initWithCapacity:self.contentLength]; 46 | } else { 47 | _data = [[NSMutableData alloc] init]; 48 | } 49 | if (_data == nil) { 50 | if (error) { 51 | *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Failed allocating memory" }]; 52 | } 53 | return NO; 54 | } 55 | return YES; 56 | } 57 | 58 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 59 | [_data appendData:data]; 60 | return YES; 61 | } 62 | 63 | - (BOOL)close:(NSError**)error { 64 | return YES; 65 | } 66 | 67 | - (NSString*)description { 68 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 69 | if (_data) { 70 | [description appendString:@"\n\n"]; 71 | [description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)]; 72 | } 73 | return description; 74 | } 75 | 76 | @end 77 | 78 | @implementation GCDWebServerDataRequest (Extensions) 79 | 80 | - (NSString*)text { 81 | if (_text == nil) { 82 | if ([self.contentType hasPrefix:@"text/"]) { 83 | NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); 84 | _text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; 85 | } else { 86 | GWS_DNOT_REACHED(); 87 | } 88 | } 89 | return _text; 90 | } 91 | 92 | - (id)jsonObject { 93 | if (_jsonObject == nil) { 94 | NSString* mimeType = GCDWebServerTruncateHeaderValue(self.contentType); 95 | if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) { 96 | _jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]; 97 | } else { 98 | GWS_DNOT_REACHED(); 99 | } 100 | } 101 | return _jsonObject; 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /AppHost/Boost/Preloader/preload_resources.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHNavigationBarResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKNavigationBarResponse.m 3 | 4 | // 5 | // Created by liang on 05/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AHNavigationBarResponse.h" 10 | #import "AppHostViewController.h" 11 | 12 | @interface AHNavigationBarResponse () 13 | 14 | // 以下是 short hand,都是从appHost 上的属性 15 | @property (nonatomic, copy) NSString *rightActionBarTitle; 16 | 17 | @end 18 | 19 | @implementation AHNavigationBarResponse 20 | 21 | + (NSDictionary *)supportActionList 22 | { 23 | return @{ 24 | @"goBack" : @"1", 25 | @"setNavRight_" : @"1", 26 | @"setNavTitle_" : @"1" 27 | }; 28 | } 29 | 30 | #pragma mark - inner 31 | ah_doc_begin(goBack, "h5 页面的返回,如果可以返回到上一个 h5 页面则返回上一个 h5,否则退出 webview 页面,如果是弹出的 webview,还可能关闭这个 presented 的 ViewController。") 32 | ah_doc_code(window.appHost.invoke("goBack")) 33 | ah_doc_code_expect("会关闭本页面") 34 | ah_doc_end 35 | - (void)goBack 36 | { 37 | if ([self.webView canGoBack]) { 38 | [self.webView goBack]; 39 | // 40 | [self initNavigationBarButtons]; 41 | }else{ 42 | [self didTapClose:nil]; 43 | } 44 | } 45 | 46 | - (void)didTapClose:(id)sender 47 | { 48 | [self.navigationController popViewControllerAnimated:YES]; 49 | } 50 | 51 | - (void)initNavigationBarButtons 52 | { 53 | if (self.appHost.fromPresented) { 54 | UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"关闭" style:UIBarButtonItemStylePlain target:self action:@selector(dismissViewController:)]; 55 | self.appHost.navigationItem.leftBarButtonItem = close; 56 | self.appHost.navigationItem.accessibilityHint = @"关闭 AppHost 弹窗"; 57 | } 58 | } 59 | 60 | - (void)dismissViewController:(id)sender 61 | { 62 | [self.appHost dismissViewControllerAnimated:YES completion:nil]; 63 | if ([self.appHost.appHostDelegate respondsToSelector:@selector(onResponseEventOccurred:response:)]) { 64 | [self.appHost.appHostDelegate onResponseEventOccurred:kAppHostEventDismissalFromPresented response:self]; 65 | } 66 | } 67 | 68 | #pragma mark - nav 69 | ah_doc_begin(setNavRight_, "h5 页面的返回,如果可以返回到上一个 h5 页面则返回上一个 h5,否则退出 webview 页面") 70 | ah_doc_code(window.appHost.on('navigator.rightbar.click',function(p){alert('你点击了'+p.text+'按钮')});window.appHost.invoke("setNavRight",{"text":"发射"})) 71 | ah_doc_param(text, "字符串,右上角按钮的文案") 72 | ah_doc_code_expect("右上角出现一个’发射‘按钮,点击这个按钮,会触发 h5 对右上角按钮的监听。表现:弹出 alert,文案是’你点击了发射按钮‘。") 73 | ah_doc_end 74 | - (void)setNavRight:(NSDictionary *)paramDict 75 | { 76 | NSString *title = [paramDict objectForKey:@"text"]; 77 | self.rightActionBarTitle = title; 78 | UIBarButtonItem *rightBarButton = nil; 79 | 80 | if (self.rightActionBarTitle.length > 0) { 81 | UIButton *rightBtn = [UIButton new]; 82 | [rightBtn setTitle:self.rightActionBarTitle forState:UIControlStateNormal]; 83 | [rightBtn setTitleColor:AHColorFromRGB(0x333333) forState:UIControlStateNormal]; 84 | [rightBtn addTarget:self action:@selector(didTapMore:) forControlEvents:UIControlEventTouchUpInside]; 85 | [rightBtn sizeToFit]; 86 | 87 | rightBarButton = [[UIBarButtonItem alloc] initWithCustomView:rightBtn]; 88 | } 89 | self.appHost.navigationItem.rightBarButtonItem = rightBarButton; 90 | } 91 | 92 | ah_doc_begin(setNavTitle_, "设置 webview 页面中间的标题") 93 | ah_doc_code(window.appHost.invoke("setNavTitle",{"text":"酒泉卫星发射中心"})) 94 | ah_doc_param(text, "字符串,整个 ViewController 的标题") 95 | ah_doc_code_expect("标题栏中间出现设置的文案,’酒泉卫星发射中心‘") 96 | ah_doc_end 97 | - (void)setNavTitle:(NSDictionary *)paramDict 98 | { 99 | NSString *title = [paramDict objectForKey:@"text"]; 100 | self.appHost.navigationItem.title = title; 101 | } 102 | 103 | #pragma mark - event 104 | - (void)didTapMore:(id)sender 105 | { 106 | if (self.rightActionBarTitle) { 107 | [self fire:@"navigator.rightbar.click" param:@{@"text": self.rightActionBarTitle}]; 108 | } 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Requests/GCDWebServerFileRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "GCDWebServerPrivate.h" 33 | 34 | @implementation GCDWebServerFileRequest { 35 | int _file; 36 | } 37 | 38 | - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { 39 | if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { 40 | _temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)dealloc { 46 | unlink([_temporaryPath fileSystemRepresentation]); 47 | } 48 | 49 | - (BOOL)open:(NSError**)error { 50 | _file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 51 | if (_file <= 0) { 52 | if (error) { 53 | *error = GCDWebServerMakePosixError(errno); 54 | } 55 | return NO; 56 | } 57 | return YES; 58 | } 59 | 60 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 61 | if (write(_file, data.bytes, data.length) != (ssize_t)data.length) { 62 | if (error) { 63 | *error = GCDWebServerMakePosixError(errno); 64 | } 65 | return NO; 66 | } 67 | return YES; 68 | } 69 | 70 | - (BOOL)close:(NSError**)error { 71 | if (close(_file) < 0) { 72 | if (error) { 73 | *error = GCDWebServerMakePosixError(errno); 74 | } 75 | return NO; 76 | } 77 | #ifdef __GCDWEBSERVER_ENABLE_TESTING__ 78 | NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"]; 79 | if (creationDateHeader) { 80 | NSDate* date = GCDWebServerParseISO8601(creationDateHeader); 81 | if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:_temporaryPath error:error]) { 82 | return NO; 83 | } 84 | } 85 | NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"]; 86 | if (modifiedDateHeader) { 87 | NSDate* date = GCDWebServerParseRFC822(modifiedDateHeader); 88 | if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate : date} ofItemAtPath:_temporaryPath error:error]) { 89 | return NO; 90 | } 91 | } 92 | #endif 93 | return YES; 94 | } 95 | 96 | - (NSString*)description { 97 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 98 | [description appendFormat:@"\n\n{%@}", _temporaryPath]; 99 | return description; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Responses/GCDWebServerErrorResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerDataResponse.h" 29 | #import "GCDWebServerHTTPStatusCodes.h" 30 | 31 | NS_ASSUME_NONNULL_BEGIN 32 | 33 | /** 34 | * The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates 35 | * an HTML body from an HTTP status code and an error message. 36 | */ 37 | @interface GCDWebServerErrorResponse : GCDWebServerDataResponse 38 | 39 | /** 40 | * Creates a client error response with the corresponding HTTP status code. 41 | */ 42 | + (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 43 | 44 | /** 45 | * Creates a server error response with the corresponding HTTP status code. 46 | */ 47 | + (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 48 | 49 | /** 50 | * Creates a client error response with the corresponding HTTP status code 51 | * and an underlying NSError. 52 | */ 53 | + (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 54 | 55 | /** 56 | * Creates a server error response with the corresponding HTTP status code 57 | * and an underlying NSError. 58 | */ 59 | + (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 60 | 61 | /** 62 | * Initializes a client error response with the corresponding HTTP status code. 63 | */ 64 | - (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 65 | 66 | /** 67 | * Initializes a server error response with the corresponding HTTP status code. 68 | */ 69 | - (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 70 | 71 | /** 72 | * Initializes a client error response with the corresponding HTTP status code 73 | * and an underlying NSError. 74 | */ 75 | - (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 76 | 77 | /** 78 | * Initializes a server error response with the corresponding HTTP status code 79 | * and an underlying NSError. 80 | */ 81 | - (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 82 | 83 | @end 84 | 85 | NS_ASSUME_NONNULL_END 86 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostEnum.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostEnum.h 3 | // AppHost 4 | // 5 | // Created by liang on 2018/12/28. 6 | // Copyright © 2018 liang. All rights reserved. 7 | // 8 | 9 | #ifndef AppHostEnum_h 10 | #define AppHostEnum_h 11 | 12 | // 创建一个超级厉害的宏,https://www.jianshu.com/p/cbb6b71d925d 13 | // 在 debug 模式下打印带前缀的日志,非 debug 模式下,不输出。 14 | #if !defined(AHLog) 15 | #ifdef AH_DEBUG 16 | #define AHLog(format, ...) do {\ 17 | (NSLog)((@"[AppHost] " format), ##__VA_ARGS__); \ 18 | } while (0) 19 | #else 20 | #define AHLog(format, ...) 21 | #endif 22 | #endif 23 | 24 | //获取设备的物理高度 25 | #define AH_SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height 26 | //获取设备的物理宽度 27 | #define AH_SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width 28 | #define AH_IS_SCREEN_HEIGHT_X (AH_SCREEN_HEIGHT == 812.0f || AH_SCREEN_HEIGHT == 896.0f) 29 | 30 | #define AH_PURE_NAVBAR_HEIGHT 44 //单纯的导航的高度 31 | #define AH_NAVIGATION_BAR_HEIGHT (AH_PURE_NAVBAR_HEIGHT + [[UIApplication sharedApplication] statusBarFrame].size.height) //顶部(导航+状态栏)的高度 32 | 33 | #define AHColorFromRGB(rgbValue) [UIColor \ 34 | colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \ 35 | green:((float)((rgbValue & 0x00FF00) >> 8))/255.0 \ 36 | blue:((float)(rgbValue & 0x0000FF))/255.0 \ 37 | alpha:1.0] 38 | 39 | #define AHColorFromRGBA(rgbValue, alphaValue) [UIColor \ 40 | colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \ 41 | green:((float)((rgbValue & 0x00FF00) >> 8))/255.0 \ 42 | blue:((float)(rgbValue & 0x0000FF))/255.0 \ 43 | alpha:alphaValue] 44 | // 定义多行文字 45 | #define ah_ml(str) @#str 46 | //# 字段含义说明; 47 | //# 1. name 表示接口名称,是 invoke 或者 call 之后的第一个参数 48 | //# 2. code 调用实例。注意,这里必须是可以真正运行的代码,因为这个字段会被作为测试用例,直接运行 49 | //# 特别注意,当这个 hash 对象有 expectFunc 字段时, 50 | //# 需要在 code 的源码里有回调去验证执行的结果是否符合预期。需要调用 window.report 接口。详细用例可参考 LocalStorage.getItem 字条 51 | //# 3. discuss 描述接口的作用 52 | //# 4. expect 表示执行 code 源码之后的效果或者结果,文字描述 53 | //# 5. expectFunc ,可选项,表示 code 之后,可验证的数据,用于 自动化测试框架验证。 54 | //# 6. autoTest ,可选项,默认为 false,表示可以响应自动化测试。false 表示不能响应,需要手动验证。 false 多见于那些页面跳转的逻辑接口 55 | // 56 | //# 注意其中,code 字段里的调用如果有 callback function,则需要自行验证,并且把结果, 57 | //# 使用 window.report(res, eleId) 报告,其中可以用 eleId 参数。此参数代表测试用例关联的元素, 用来显示测试结果。 58 | //# 其中,res 是 true、false、finish、tapApp、navBack、swipeDown、swipeLeft、sleep, 等的枚举值 59 | //# eleId 是 当前测试用例所在的行数。传入 eleId,无需修改。 60 | 61 | #define ah_concat(A, B) A##B 62 | #define ah_doc_log_prefix @"ah_doc_for_" 63 | #define ah_doc_selector(name) NSSelectorFromString([NSString stringWithFormat:@"%@%@", ah_doc_log_prefix, name]) 64 | // 定义 oc-doc,为自动化生成测试代码和自动化注释做准备 65 | // 凡是可能多行的文字描述都用 @result,传入的数据需要有双引号,而不是@#result 66 | 67 | #define ah_doc_begin(signature, desc) +(NSDictionary *)ah_concat(ah_doc_for_, signature)\ 68 | {\ 69 | NSMutableArray *lst = [NSMutableArray arrayWithCapacity:3];\ 70 | NSMutableDictionary *docs = [@{\ 71 | @"name":@#signature,\ 72 | @"discuss":@desc\ 73 | } mutableCopy]; 74 | #define ah_doc_code(code) [docs setObject:@#code forKey:@"code"]; 75 | 76 | #define ah_doc_code_expect(result) [docs setObject:@result forKey:@"expect"]; 77 | #define ah_doc_code_expectFunc(result) [docs setObject:@result forKey:@"expectFunc"]; 78 | #define ah_doc_code_autoTest(result) [docs setObject:@result forKey:@"autoTest"]; 79 | 80 | #define ah_doc_param(field, desc) [lst addObject:@{@#field:@desc}]; 81 | 82 | #define ah_doc_return(type, desc) [docs setObject:@{@#type:@desc} forKey:@"return"]; 83 | 84 | #define ah_doc_end if(lst.count > 0){\ 85 | [docs setObject:lst forKey:@"param"];\ 86 | }\ 87 | return docs;\ 88 | } 89 | // oc-doc 结束 90 | 91 | #endif /* AppHostEnum_h */ 92 | 93 | #define NOW_TIME [[NSDate date] timeIntervalSince1970] * 1000 94 | 95 | // 为了解决 webview Cookie 而需要提前加载的页面 96 | extern NSString * _Nonnull kFakeCookieWebPageURLWithQueryString; 97 | // 设置进度条的颜色,如 "0xff00ff"; 98 | extern long long kWebViewProgressTintColorRGB; 99 | // 是否打开 debug server 的日志。 100 | extern BOOL kGCDWebServer_logging_enabled; 101 | 102 | static NSString * _Nonnull kAHLogoutNotification = @"kAHLogoutNotification"; 103 | static NSString * _Nonnull kAHLoginSuccessNotification = @"kAHLoginSuccessNotification"; 104 | 105 | static NSString * _Nonnull kAppHostEventDismissalFromPresented = @"kAppHostEventDismissalFromPresented"; 106 | // core 107 | static NSString * _Nonnull kAHActionKey = @"action"; 108 | static NSString * _Nonnull kAHParamKey = @"param"; 109 | -------------------------------------------------------------------------------- /AppHost/RemoteDebug/testcase.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 86 | AppHost 接口测试 87 | 88 | 89 | 90 |
91 |
92 | 测试webview浏览器 93 |
    94 |
  1. 95 | 96 |
    97 | 98 | 使用webview页面,加载以上链接 99 |
  2. 100 |
  3. 101 | 102 |
  4. 103 |
104 |
105 | {{ALL_DOCS}} 106 |
107 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Core/GCDWebServerFunctions.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | /** 37 | * Converts a file extension to the corresponding MIME type. 38 | * If there is no match, "application/octet-stream" is returned. 39 | * 40 | * Overrides allow to customize the built-in mapping from extensions to MIME 41 | * types. Keys of the dictionary must be lowercased file extensions without 42 | * the period, and the values must be the corresponding MIME types. 43 | */ 44 | NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* _Nullable overrides); 45 | 46 | /** 47 | * Add percent-escapes to a string so it can be used in a URL. 48 | * The legal characters ":@/?&=+" are also escaped to ensure compatibility 49 | * with URL encoded forms and URL queries. 50 | */ 51 | NSString* _Nullable GCDWebServerEscapeURLString(NSString* string); 52 | 53 | /** 54 | * Unescapes a URL percent-encoded string. 55 | */ 56 | NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string); 57 | 58 | /** 59 | * Extracts the unescaped names and values from an 60 | * "application/x-www-form-urlencoded" form. 61 | * http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 62 | */ 63 | NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form); 64 | 65 | /** 66 | * On OS X, returns the IPv4 or IPv6 address as a string of the primary 67 | * connected service or nil if not available. 68 | * 69 | * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi 70 | * interface if connected or nil otherwise. 71 | */ 72 | NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6); 73 | 74 | /** 75 | * Converts a date into a string using RFC822 formatting. 76 | * https://tools.ietf.org/html/rfc822#section-5 77 | * https://tools.ietf.org/html/rfc1123#section-5.2.14 78 | */ 79 | NSString* GCDWebServerFormatRFC822(NSDate* date); 80 | 81 | /** 82 | * Converts a RFC822 formatted string into a date. 83 | * https://tools.ietf.org/html/rfc822#section-5 84 | * https://tools.ietf.org/html/rfc1123#section-5.2.14 85 | * 86 | * @warning Timezones other than GMT are not supported by this function. 87 | */ 88 | NSDate* _Nullable GCDWebServerParseRFC822(NSString* string); 89 | 90 | /** 91 | * Converts a date into a string using IOS 8601 formatting. 92 | * http://tools.ietf.org/html/rfc3339#section-5.6 93 | */ 94 | NSString* GCDWebServerFormatISO8601(NSDate* date); 95 | 96 | /** 97 | * Converts a ISO 8601 formatted string into a date. 98 | * http://tools.ietf.org/html/rfc3339#section-5.6 99 | * 100 | * @warning Only "calendar" variant is supported at this time and timezones 101 | * other than GMT are not supported either. 102 | */ 103 | NSDate* _Nullable GCDWebServerParseISO8601(NSString* string); 104 | 105 | #ifdef __cplusplus 106 | } 107 | #endif 108 | 109 | NS_ASSUME_NONNULL_END 110 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Responses/GCDWebServerDataResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerResponse.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body 34 | * of the HTTP response from memory. 35 | */ 36 | @interface GCDWebServerDataResponse : GCDWebServerResponse 37 | @property(nonatomic, copy) NSString* contentType; // Redeclare as non-null 38 | 39 | /** 40 | * Creates a response with data in memory and a given content type. 41 | */ 42 | + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type; 43 | 44 | /** 45 | * This method is the designated initializer for the class. 46 | */ 47 | - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type; 48 | 49 | @end 50 | 51 | @interface GCDWebServerDataResponse (Extensions) 52 | 53 | /** 54 | * Creates a data response from text encoded using UTF-8. 55 | */ 56 | + (nullable instancetype)responseWithText:(NSString*)text; 57 | 58 | /** 59 | * Creates a data response from HTML encoded using UTF-8. 60 | */ 61 | + (nullable instancetype)responseWithHTML:(NSString*)html; 62 | 63 | /** 64 | * Creates a data response from an HTML template encoded using UTF-8. 65 | * See -initWithHTMLTemplate:variables: for details. 66 | */ 67 | + (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; 68 | 69 | /** 70 | * Creates a data response from a serialized JSON object and the default 71 | * "application/json" content type. 72 | */ 73 | + (nullable instancetype)responseWithJSONObject:(id)object; 74 | 75 | /** 76 | * Creates a data response from a serialized JSON object and a custom 77 | * content type. 78 | */ 79 | + (nullable instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type; 80 | + (instancetype)responseWithText:(NSString *)object contentType:(NSString*)type ; 81 | /** 82 | * Initializes a data response from text encoded using UTF-8. 83 | */ 84 | - (nullable instancetype)initWithText:(NSString*)text; 85 | - (instancetype)initWithText:(NSString*)text contentType:(NSString*)type; 86 | /** 87 | * Initializes a data response from HTML encoded using UTF-8. 88 | */ 89 | - (nullable instancetype)initWithHTML:(NSString*)html; 90 | 91 | /** 92 | * Initializes a data response from an HTML template encoded using UTF-8. 93 | * 94 | * All occurences of "%variable%" within the HTML template are replaced with 95 | * their corresponding values. 96 | */ 97 | - (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; 98 | 99 | /** 100 | * Initializes a data response from a serialized JSON object and the default 101 | * "application/json" content type. 102 | */ 103 | - (nullable instancetype)initWithJSONObject:(id)object; 104 | 105 | /** 106 | * Initializes a data response from a serialized JSON object and a custom 107 | * content type. 108 | */ 109 | - (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type; 110 | 111 | @end 112 | 113 | NS_ASSUME_NONNULL_END 114 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHNavigationResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKNavigationResponse.m 3 | 4 | // 5 | // Created by liang on 05/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AHNavigationResponse.h" 10 | #import "AppHostViewController.h" 11 | 12 | @import SafariServices; 13 | 14 | @implementation AHNavigationResponse 15 | 16 | + (NSDictionary *)supportActionList 17 | { 18 | return @{ 19 | //增加apphost的supportTypeFunction 20 | @"startNewPage_" : @"4", 21 | @"openExternalUrl_" : @"3" 22 | }; 23 | } 24 | 25 | #pragma mark - inner 26 | ah_doc_begin(openExternalUrl_, "打开外部资源链接,可以用 SFSafariViewController 打开,也可以用系统的 Safari 浏览器打开。") 27 | ah_doc_code(window.appHost.invoke("openExternalUrl",{"url":"https://qian.163.com"})) 28 | ah_doc_param(url, "字符串,合法的 url 地址,包括http/mailto:/telephone:/https 前缀") 29 | ah_doc_param(openInSafari, "布尔值,默认是 false,表示在 App 内部用 SFSafariViewController 内部打开;true 表示用系统的 Safari 浏览器打开") 30 | ah_doc_code_expect("在 App 内的浏览器里打开了’qian.163.com‘ 页面") 31 | ah_doc_end 32 | - (void)openExternalUrl:(NSDictionary *)paramDict 33 | { 34 | NSString *urlTxt = [paramDict objectForKey:@"url"]; 35 | BOOL forceOpenInSafari = [[paramDict objectForKey:@"openInSafari"] boolValue]; 36 | if (forceOpenInSafari) { 37 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlTxt] options:@{} completionHandler:nil]; 38 | } else { 39 | SFSafariViewController *safari = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:urlTxt]]; 40 | [self.navigationController presentViewController:safari animated:YES completion:nil]; 41 | } 42 | } 43 | 44 | - (void)insertShadowView:(NSDictionary *)paramDict 45 | { 46 | AppHostViewController *freshOne = [[self.appHost.class alloc] init]; 47 | freshOne.url = [paramDict objectForKey:@"url"]; 48 | freshOne.pageTitle = [paramDict objectForKey:@"title"]; 49 | freshOne.rightActionBarTitle = [paramDict objectForKey:@"actionTitle"]; 50 | freshOne.backPageParameter = [paramDict objectForKey:@"backPageParameter"]; 51 | // 52 | NSArray *viewControllers = self.navigationController.viewControllers; 53 | 54 | if (viewControllers.count > 0) { 55 | //在A->B页面里,点击返回到C,然后C返回到A,形成 A-C-B,简化下成A——C; 56 | NSMutableArray *newViewControllers = [viewControllers mutableCopy]; 57 | [newViewControllers addObject:freshOne]; 58 | freshOne.hidesBottomBarWhenPushed = YES; 59 | self.navigationController.viewControllers = newViewControllers; 60 | } 61 | } 62 | ah_doc_begin(startNewPage_, "新开一个 webview 页面打开目标 url。有多个参数可以控制 webview 的样式和行为") 63 | ah_doc_code(window.appHost.invoke('startNewPage', { 'url': 'http://you.163.com/','title': 'title', 64 | 'type': "push", 65 | 'backPageParameter': { 66 | 'url': 'http://qian.163.com', 67 | 'title': 'title', 68 | 'type': 'push' 69 | } 70 | })) 71 | ah_doc_param(url, "字符串,合法的 url 地址,包括http/mailto:/telephone:/https 前缀") 72 | ah_doc_param(title,"当前页面的标题") 73 | ah_doc_param(type,"新页面呈现方式,目前有两个参数可选“push”,“replace” ") 74 | ah_doc_param(actionTitle,"顶栏右边的文字,可以响应点击事件。") 75 | ah_doc_param(backPageParameter,"完整的一个startNewPage对应的参数; 这个参数代表了页面 c,包含这个参数的跳转执行完毕之后,到达 b 页面,此时点击返回按钮,返回到 c页面,再次点击才返回到 a 页面。即 a -> b , b -> c -> a;") 76 | ah_doc_code_expect("新开一个 webview 打开’you.163.com‘页面,加载完毕之后,点击返回,返回到 ’qian.163.com‘ 页面") 77 | ah_doc_end 78 | - (void)startNewPage:(NSDictionary *)paramDict 79 | { 80 | AppHostViewController *freshOne = [[self.appHost.class alloc] init]; 81 | freshOne.url = [paramDict objectForKey:@"url"]; 82 | freshOne.pageTitle = [paramDict objectForKey:@"title"]; 83 | freshOne.rightActionBarTitle = [paramDict objectForKey:@"actionTitle"]; 84 | 85 | freshOne.backPageParameter = [paramDict objectForKey:@"backPageParameter"]; 86 | NSString *loadType = [paramDict objectForKey:@"type"]; 87 | if (freshOne.backPageParameter) { 88 | //额外插入一个页面; 89 | [self insertShadowView:freshOne.backPageParameter]; 90 | } 91 | if ([@"replace" isEqualToString:loadType]) { 92 | NSArray *viewControllers = self.navigationController.viewControllers; 93 | 94 | if (viewControllers.count > 1) { 95 | // replace的目的就是调整到新的list页面;需要替换旧list和新的回复页面; 96 | NSMutableArray *newViewControllers = [[viewControllers subarrayWithRange:NSMakeRange(0, [viewControllers count] - 2)] mutableCopy]; 97 | [newViewControllers addObject:freshOne]; 98 | freshOne.hidesBottomBarWhenPushed = YES; 99 | [self.navigationController setViewControllers:newViewControllers animated:YES]; 100 | } else { 101 | [self.navigationController popViewControllerAnimated:YES]; 102 | } 103 | } else { 104 | [self.navigationController pushViewController:freshOne animated:YES]; 105 | } 106 | } 107 | @end 108 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Responses/GCDWebServerFileResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerResponse.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body 34 | * of the HTTP response from a file on disk. 35 | * 36 | * It will automatically set the contentType, lastModifiedDate and eTag 37 | * properties of the GCDWebServerResponse according to the file extension and 38 | * metadata. 39 | */ 40 | @interface GCDWebServerFileResponse : GCDWebServerResponse 41 | @property(nonatomic, copy) NSString* contentType; // Redeclare as non-null 42 | @property(nonatomic) NSDate* lastModifiedDate; // Redeclare as non-null 43 | @property(nonatomic, copy) NSString* eTag; // Redeclare as non-null 44 | 45 | /** 46 | * Creates a response with the contents of a file. 47 | */ 48 | + (nullable instancetype)responseWithFile:(NSString*)path; 49 | 50 | /** 51 | * Creates a response like +responseWithFile: and sets the "Content-Disposition" 52 | * HTTP header for a download if the "attachment" argument is YES. 53 | */ 54 | + (nullable instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; 55 | 56 | /** 57 | * Creates a response like +responseWithFile: but restricts the file contents 58 | * to a specific byte range. 59 | * 60 | * See -initWithFile:byteRange: for details. 61 | */ 62 | + (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range; 63 | 64 | /** 65 | * Creates a response like +responseWithFile:byteRange: and sets the 66 | * "Content-Disposition" HTTP header for a download if the "attachment" 67 | * argument is YES. 68 | */ 69 | + (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; 70 | 71 | /** 72 | * Initializes a response with the contents of a file. 73 | */ 74 | - (nullable instancetype)initWithFile:(NSString*)path; 75 | 76 | /** 77 | * Initializes a response like +responseWithFile: and sets the 78 | * "Content-Disposition" HTTP header for a download if the "attachment" 79 | * argument is YES. 80 | */ 81 | - (nullable instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; 82 | 83 | /** 84 | * Initializes a response like -initWithFile: but restricts the file contents 85 | * to a specific byte range. This range should be set to (NSUIntegerMax, 0) for 86 | * the full file, (offset, length) if expressed from the beginning of the file, 87 | * or (NSUIntegerMax, length) if expressed from the end of the file. The "offset" 88 | * and "length" values will be automatically adjusted to be compatible with the 89 | * actual size of the file. 90 | * 91 | * This argument would typically be set to the value of the byteRange property 92 | * of the current GCDWebServerRequest. 93 | */ 94 | - (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range; 95 | 96 | /** 97 | * This method is the designated initializer for the class. 98 | * 99 | * If MIME type overrides are specified, they allow to customize the built-in 100 | * mapping from extensions to MIME types. Keys of the dictionary must be lowercased 101 | * file extensions without the period, and the values must be the corresponding 102 | * MIME types. 103 | */ 104 | - (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary*)overrides; 105 | 106 | @end 107 | 108 | NS_ASSUME_NONNULL_END 109 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKAppHostResponse.m 3 | 4 | // 5 | // Created by liang on 05/01/2018. 6 | // Copyright © 2018 smilly.co All rights reserved. 7 | // 8 | 9 | #import "AppHostResponse.h" 10 | #import "AppHostViewController.h" 11 | #import "AppHostViewController+Scripts.h" 12 | #import 13 | 14 | @interface AppHostResponse () 15 | 16 | @property (nonatomic, weak, readwrite) WKWebView *webView; 17 | 18 | @property (nonatomic, weak, readwrite) UINavigationController *navigationController; 19 | 20 | @property (nonatomic, weak, readwrite) AppHostViewController *appHost; 21 | 22 | @end 23 | 24 | @implementation AppHostResponse 25 | 26 | - (instancetype)initWithAppHost:(AppHostViewController *)appHost 27 | { 28 | if (self = [self init]) { 29 | self.webView = appHost.webView; 30 | self.navigationController = appHost.navigationController; 31 | self.appHost = appHost; 32 | } 33 | 34 | return self; 35 | } 36 | 37 | - (void)fireCallback:(NSString *)callbackKey param:(NSDictionary *)paramDict 38 | { 39 | [self.appHost fireCallback:callbackKey param:paramDict]; 40 | } 41 | 42 | - (void)fire:(NSString *)actionName param:(NSDictionary *)paramDict 43 | { 44 | [self.appHost fire:actionName param:paramDict]; 45 | } 46 | 47 | - (void)dealloc 48 | { 49 | _webView = nil; 50 | self.navigationController = nil; 51 | self.appHost = nil; 52 | } 53 | 54 | #pragma mark - protocol 55 | 56 | - (BOOL)handleAction:(NSString *)action withParam:(NSDictionary *)paramDict callbackKey:(NSString *)callbackKey; 57 | { 58 | if (action == nil) { 59 | return false; 60 | } 61 | SEL sel = nil; 62 | if (paramDict == nil || paramDict.allKeys.count == 0) { 63 | if (callbackKey.length == 0) { 64 | sel = NSSelectorFromString([NSString stringWithFormat:@"%@", action]); 65 | } else { 66 | sel = NSSelectorFromString([NSString stringWithFormat:@"%@WithCallback:", action]); 67 | } 68 | } else { 69 | if (callbackKey.length == 0) { 70 | sel = NSSelectorFromString([NSString stringWithFormat:@"%@:", action]); 71 | } else { 72 | sel = NSSelectorFromString([NSString stringWithFormat:@"%@:callback:", action]); 73 | } 74 | } 75 | 76 | if (![self respondsToSelector:sel]) { 77 | return NO; 78 | } 79 | [self runSelector:sel withObjects:[NSArray arrayWithObjects:paramDict, callbackKey, nil]]; 80 | return YES; 81 | } 82 | 83 | - (id)runSelector:(SEL)aSelector withObjects:(NSArray *)objects { 84 | NSMethodSignature *methodSignature = [self methodSignatureForSelector:aSelector]; 85 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; 86 | [invocation setTarget:self]; 87 | [invocation setSelector:aSelector]; 88 | 89 | NSUInteger i = 1; 90 | 91 | if (objects.count) { 92 | for (id object in objects) { 93 | id tempObject = object; 94 | [invocation setArgument:&tempObject atIndex:++i]; 95 | } 96 | } 97 | [invocation invoke]; 98 | 99 | if (methodSignature.methodReturnLength > 0) { 100 | id value; 101 | [invocation getReturnValue:&value]; 102 | return value; 103 | } 104 | return nil; 105 | } 106 | 107 | + (BOOL)isSupportedActionSignature:(NSString *)signature 108 | { 109 | NSDictionary *support = [self supportActionList]; 110 | 111 | // 如果数值大于0,表示是支持的,返回 YES 112 | if ([[support objectForKey:signature] integerValue] > 0) { 113 | return YES; 114 | } 115 | return NO; 116 | } 117 | 118 | + (NSDictionary *)supportActionList 119 | { 120 | NSAssert(NO, @"Must implement handleActionFromH5 method"); 121 | return @{}; 122 | } 123 | #pragma - doc 124 | /** 125 | TODO 可变参数如何传参?解决代码copy的问题 126 | 解决生成 ah_doc 的文档里的参数对象 127 | 128 | @param desc 默认描述,如果是偶数个参数,则生成 param 对象。如果是单个参数则认为整体参数描述,不细分为小参数 129 | @return 返回一个字段对象 130 | */ 131 | + (NSDictionary *)getParams:(NSString *)desc, ... NS_REQUIRES_NIL_TERMINATION; 132 | { 133 | NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:4]; 134 | 135 | va_list arg_list; 136 | va_start(arg_list, desc);// 获取后续参数的偏移 137 | NSString *device = va_arg(arg_list, NSString *); 138 | NSMutableArray *lst = [NSMutableArray arrayWithCapacity:3]; 139 | if(device){ 140 | [lst addObject:[device copy]]; 141 | } 142 | 143 | while(device){ 144 | device = va_arg(arg_list, NSString *); 145 | [lst addObject:[device copy]]; 146 | } 147 | va_end(arg_list); 148 | 149 | if(lst.count == 1){ 150 | [result setObject:[lst firstObject] forKey:@"paraDict"]; 151 | } else if(lst.count > 1){ 152 | // 153 | NSInteger count = lst.count / 2; 154 | for (NSInteger i = 0; i < count; i++){ 155 | [result setObject:[lst objectAtIndex:i * 2 + 1] forKey:[lst objectAtIndex:i * 2]]; 156 | } 157 | } 158 | return result; 159 | } 160 | @end 161 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerRequest.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerMultiPart class is an abstract class that wraps the content 34 | * of a part. 35 | */ 36 | @interface GCDWebServerMultiPart : NSObject 37 | 38 | /** 39 | * Returns the control name retrieved from the part headers. 40 | */ 41 | @property(nonatomic, readonly) NSString* controlName; 42 | 43 | /** 44 | * Returns the content type retrieved from the part headers or "text/plain" 45 | * if not available (per HTTP specifications). 46 | */ 47 | @property(nonatomic, readonly) NSString* contentType; 48 | 49 | /** 50 | * Returns the MIME type component of the content type for the part. 51 | */ 52 | @property(nonatomic, readonly) NSString* mimeType; 53 | 54 | @end 55 | 56 | /** 57 | * The GCDWebServerMultiPartArgument subclass of GCDWebServerMultiPart wraps 58 | * the content of a part as data in memory. 59 | */ 60 | @interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart 61 | 62 | /** 63 | * Returns the data for the part. 64 | */ 65 | @property(nonatomic, readonly) NSData* data; 66 | 67 | /** 68 | * Returns the data for the part interpreted as text. If the content 69 | * type of the part is not a text one, or if an error occurs, nil is returned. 70 | * 71 | * The text encoding used to interpret the data is extracted from the 72 | * "Content-Type" header or defaults to UTF-8. 73 | */ 74 | @property(nonatomic, readonly, nullable) NSString* string; 75 | 76 | @end 77 | 78 | /** 79 | * The GCDWebServerMultiPartFile subclass of GCDWebServerMultiPart wraps 80 | * the content of a part as a file on disk. 81 | */ 82 | @interface GCDWebServerMultiPartFile : GCDWebServerMultiPart 83 | 84 | /** 85 | * Returns the file name retrieved from the part headers. 86 | */ 87 | @property(nonatomic, readonly) NSString* fileName; 88 | 89 | /** 90 | * Returns the path to the temporary file containing the part data. 91 | * 92 | * @warning This temporary file will be automatically deleted when the 93 | * GCDWebServerMultiPartFile is deallocated. If you want to preserve this file, 94 | * you must move it to a different location beforehand. 95 | */ 96 | @property(nonatomic, readonly) NSString* temporaryPath; 97 | 98 | @end 99 | 100 | /** 101 | * The GCDWebServerMultiPartFormRequest subclass of GCDWebServerRequest 102 | * parses the body of the HTTP request as a multipart encoded form. 103 | */ 104 | @interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest 105 | 106 | /** 107 | * Returns the argument parts from the multipart encoded form as 108 | * name / GCDWebServerMultiPartArgument pairs. 109 | */ 110 | @property(nonatomic, readonly) NSArray* arguments; 111 | 112 | /** 113 | * Returns the files parts from the multipart encoded form as 114 | * name / GCDWebServerMultiPartFile pairs. 115 | */ 116 | @property(nonatomic, readonly) NSArray* files; 117 | 118 | /** 119 | * Returns the MIME type for multipart encoded forms 120 | * i.e. "multipart/form-data". 121 | */ 122 | + (NSString*)mimeType; 123 | 124 | /** 125 | * Returns the first argument for a given control name or nil if not found. 126 | */ 127 | - (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name; 128 | 129 | /** 130 | * Returns the first file for a given control name or nil if not found. 131 | */ 132 | - (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name; 133 | 134 | @end 135 | 136 | NS_ASSUME_NONNULL_END 137 | -------------------------------------------------------------------------------- /AppHost/Core/AppHostViewController+Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppHostViewController+Utils.m 3 | // AppHost 4 | // 5 | // Created by liang on 2019/3/23. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AppHostViewController+Utils.h" 10 | #import "AppHostViewController+Dispatch.h" 11 | #import "AHWebViewScrollPositionManager.h" 12 | #import "AHResponseManager.h" 13 | 14 | @implementation AppHostViewController (Utils) 15 | 16 | #pragma mark - supportType 17 | - (NSDictionary *)supportListByNow 18 | { 19 | //人肉维护支持列表; 20 | NSMutableDictionary *supportedFunctions = [@{ 21 | //增加apphost的supportTypeFunction 22 | @"pageshow" : @"2", 23 | @"pagehide" : @"2" 24 | } mutableCopy]; 25 | // 内置接口 26 | // 各个response 的 supportFunction 27 | [[AHResponseManager defaultManager].customResponseClasses enumerateObjectsUsingBlock:^(Class resp, NSUInteger idx, BOOL * _Nonnull stop) { 28 | [supportedFunctions addEntriesFromDictionary:[resp supportActionList]]; 29 | }]; 30 | 31 | 32 | NSMutableDictionary *lst = [NSMutableDictionary dictionaryWithCapacity:10]; 33 | [lst setObject:supportedFunctions forKey:@"supportFunctionType"]; 34 | 35 | NSMutableDictionary *appInfo = [NSMutableDictionary dictionaryWithCapacity:10]; 36 | if (AH_IS_SCREEN_HEIGHT_X) { 37 | [appInfo setObject:@{ @"iPhoneXVersion" : @"1" } forKey:@"iPhoneXInfo"]; 38 | } 39 | 40 | [lst setObject:appInfo forKey:@"appInfo"]; 41 | return lst; 42 | } 43 | 44 | #pragma mark - shim 45 | 46 | - (void)showTextTip:(NSString *)text 47 | { 48 | [self showTextTip:text hideAfterDelay:2.f]; 49 | } 50 | 51 | - (void)showTextTip:(NSString *)text hideAfterDelay:(CGFloat)delay 52 | { 53 | [self callNative:@"showTextTip" parameter:@{ 54 | @"text" : text ?: @"<空>", 55 | @"hideAfterDelay" : @(delay > 0 ?: 2.f) 56 | }]; 57 | } 58 | 59 | #pragma mark - view history 60 | - (void)dealWithViewHistory 61 | { 62 | if (self.disableScrollPositionMemory) { 63 | return; 64 | } 65 | 66 | NSURL *url = self.webView.URL; 67 | UIScrollView *sv = self.webView.scrollView; 68 | CGFloat oldY = [[AHWebViewScrollPositionManager sharedInstance] positionForCacheURL:url]; 69 | if (oldY != sv.contentOffset.y) { 70 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 71 | sv.contentOffset = CGPointMake(0, oldY); 72 | }); 73 | } 74 | } 75 | 76 | 77 | #pragma mark - scrollview delegate 78 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 79 | { 80 | [self scrollViewDidEndDecelerating:scrollView]; 81 | } 82 | 83 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 84 | { 85 | if (self.disableScrollPositionMemory) { 86 | return; 87 | } 88 | CGFloat y = scrollView.contentOffset.y; 89 | NSLog(@"contentOffset.y = %.2f", y); 90 | [[AHWebViewScrollPositionManager sharedInstance] cacheURL:self.webView.URL position:y]; 91 | } 92 | 93 | #pragma mark - utils 94 | 95 | - (void)popOutImmediately 96 | { 97 | if (self.fromPresented) { 98 | [self dismissViewControllerAnimated:NO completion:nil]; 99 | } else { 100 | [self.navigationController popViewControllerAnimated:NO]; 101 | } 102 | } 103 | 104 | - (BOOL)isExternalSchemeRequest:(NSString *)url 105 | { 106 | NSArray *prefixs = @[ @"http://", @"https://"]; 107 | BOOL __block external = YES; 108 | 109 | [prefixs enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 110 | if ([url hasPrefix:obj]) { 111 | external = NO; 112 | *stop = YES; 113 | } 114 | }]; 115 | 116 | return external; 117 | } 118 | 119 | static NSString *const kAHRequestItmsApp = @"itms-apps://"; 120 | - (BOOL)isItmsAppsRequest:(NSString *)url 121 | { 122 | // itms-appss://itunes.apple.com/cn/app/id992055304 123 | // https://itunes.apple.com/cn/app/id992055304 124 | NSArray *prefixs = @[ kAHRequestItmsApp, @"https://itunes.apple.com", @"itms-appss://", @"itms-services://", @"itmss://" ]; 125 | BOOL __block pass = NO; 126 | 127 | [prefixs enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 128 | if ([url hasPrefix:obj]) { 129 | pass = YES; 130 | *stop = YES; 131 | } 132 | }]; 133 | 134 | return pass; 135 | } 136 | 137 | - (void)logRequestAndResponse:(NSString *)str type:(NSString *)type 138 | { 139 | NSInteger limit = 500; 140 | if (str.length > limit) { 141 | AHLog(@"debug type: %@ , url : %@", type, [str substringToIndex:limit]); 142 | } else { 143 | AHLog(@"debug type: %@ , url : %@", type, str); 144 | } 145 | } 146 | 147 | @end 148 | -------------------------------------------------------------------------------- /AppHost/RemoteDebug/server.css: -------------------------------------------------------------------------------- 1 | /** widget **/ 2 | .f-hide{ 3 | display: none; 4 | } 5 | /** global**/ 6 | html, 7 | body { 8 | width: 100%; 9 | height: 100%; 10 | padding: 0; 11 | margin: 0; 12 | overflow: hidden; 13 | } 14 | 15 | #app { 16 | height: 100%; 17 | width: 100%; 18 | overflow-y: scroll; 19 | overflow-x: hidden; 20 | } 21 | 22 | .c-garden { 23 | padding: 0 30px 10px 80px; 24 | } 25 | 26 | .c-tool-panel { 27 | position: fixed; 28 | left: 15px; 29 | top: 40%; 30 | width: 50px; 31 | height: 180px; 32 | display: flex; 33 | flex-direction: column; 34 | background: #E3EDFF; 35 | align-items: center; 36 | justify-content: space-evenly; 37 | border-radius: 5px; 38 | } 39 | 40 | .w-tool-item { 41 | font-size: 12px; 42 | color: #4468b9; 43 | font-weight: bold; 44 | border-radius: 2px; 45 | padding: 2px; 46 | box-sizing: border-box; 47 | width: 100%; 48 | text-align: center; 49 | vertical-align: middle; 50 | text-decoration: underline; 51 | cursor: pointer; 52 | } 53 | /** 下面命令行区域 **/ 54 | .m-output { 55 | width: 100%; 56 | box-sizing: border-box; 57 | font-weight: 200; 58 | } 59 | 60 | .m-output-result { 61 | border-bottom: 1px solid #E3EDFF; 62 | font-size: 12px; 63 | padding-left: 10px; 64 | } 65 | 66 | .m-input { 67 | padding: 5px 0; 68 | background-color: white; 69 | position: sticky; 70 | position: -webkit-sticky; 71 | bottom: 5px; 72 | } 73 | .m-input .w-env-icon{ 74 | width: 30px; 75 | height: 30px; 76 | position: absolute; 77 | top: 14px; 78 | left: -35px; 79 | } 80 | 81 | .m-input .w-command { 82 | width: 100%; 83 | height: 50px; 84 | padding: 0; 85 | margin: 0; 86 | border: none; 87 | /* border-top: 2px solid #E3EDFF;*/ 88 | border-bottom: 2px solid #E3EDFF; 89 | border-right: 100px solid #fff; 90 | overflow: hidden; 91 | font-size: 20px; 92 | font-weight: 200; 93 | text-indent: 10px; 94 | box-sizing: border-box; 95 | } 96 | 97 | .m-input .w-command-run { 98 | color: #333333; 99 | position: absolute; 100 | width: 90px; 101 | top: 5px; 102 | right: 5px; 103 | bottom: 5px; 104 | font-size: 25px; 105 | line-height: 50px; 106 | text-align: center; 107 | } 108 | 109 | .m-output-command { 110 | width: 100%; 111 | padding: 5px 0; 112 | margin: 0; 113 | border-top: 1.5px solid #E3EDFF; 114 | min-height: 32px; 115 | color: #333333; 116 | font-size: 16px; 117 | line-height: 32px; 118 | /* text-indent: 10px; */ 119 | } 120 | 121 | .m-output-command-error{ 122 | color: #333333; 123 | font-size: 12px; 124 | line-height: 24px; 125 | } 126 | 127 | .w-eval-succ{ 128 | color: #333333; 129 | font-size: 12px; 130 | line-height: 24px; 131 | } 132 | 133 | .w-em-seq { 134 | color: #333333; 135 | /* font-size: 14px; */ 136 | line-height: 14px; 137 | vertical-align: text-bottom; 138 | text-indent: 0; 139 | } 140 | 141 | pre { 142 | margin-top: 0; 143 | padding-left: 15px; 144 | } 145 | 146 | .m-output-type { 147 | margin-top: 5px; 148 | /* text-indent: 5px; */ 149 | text-decoration: underline; 150 | font-weight: bold; 151 | } 152 | 153 | .w-help-code { 154 | background-color: #E3EDFF; 155 | color: #000; 156 | font-size: 14px; 157 | line-height: 20px; 158 | padding: 5px; 159 | font-weight: 400; 160 | font-family: monospace; 161 | word-wrap: break-word; 162 | } 163 | 164 | .w-err-code { 165 | color: #de3535; 166 | font-size: 14px; 167 | } 168 | 169 | .w-welcome-slogan { 170 | font-size: 16px; 171 | color: #4468b9; 172 | text-align: center; 173 | height: 20px; 174 | line-height: 18px; 175 | } 176 | 177 | .c-welcome-desc { 178 | color: #af0404; 179 | font-size: 14px; 180 | text-indent: 15px; 181 | padding: 0; 182 | margin: 0; 183 | text-align: center; 184 | text-decoration: line-through; 185 | } 186 | 187 | .w-example-title { 188 | font-size: 14px; 189 | text-align: left; 190 | font-weight: bold; 191 | padding: 7px 5px; 192 | text-indent: 0; 193 | } 194 | /** 命令历史部分**/ 195 | .c-command-history{ 196 | padding: 2px 0; 197 | margin: 0; 198 | list-style: upper-roman; 199 | background-color: #E3EDFF; 200 | border-radius: 2px; 201 | } 202 | .w-history-item{ 203 | line-height: 16px; 204 | font-size: 14px; 205 | padding: 4px 0; 206 | cursor: pointer; 207 | text-decoration: underline; 208 | } 209 | /* 注释部分 */ 210 | .m-output-doc { 211 | font-size: 14px; 212 | padding: 0 15px 10px; 213 | } 214 | 215 | .w-doc-type_title { 216 | font-weight: bold; 217 | padding: 5px 0; 218 | } 219 | 220 | .w-doc-param-container { 221 | list-style-type: circle; 222 | padding-left: 20px; 223 | margin: 5px; 224 | } 225 | 226 | .w-doc-param span { 227 | font-weight: bold; 228 | } 229 | 230 | .w-doc-discuss{ 231 | margin: 3px 0; 232 | } 233 | 234 | .w-doc-param{ 235 | font-size: 12px; 236 | } 237 | 238 | .f-warning-text{ 239 | margin: 0; 240 | font-weight: bold; 241 | } 242 | 243 | .w-doc-code { 244 | background-color: #333333; 245 | padding: 10px 5px; 246 | color: white; 247 | } 248 | 249 | .w-doc-codeResult { 250 | padding: 5px 0; 251 | } 252 | 253 | .w-api_list-group { 254 | margin: 8px 0 4px; 255 | color: #4468b9; 256 | font-weight: bold; 257 | } 258 | 259 | .w-api_list-api { 260 | display: block; 261 | } 262 | 263 | .w-api_list-other { 264 | padding: 0 5px; 265 | } 266 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Responses/GCDWebServerDataResponse.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "GCDWebServerPrivate.h" 33 | 34 | @implementation GCDWebServerDataResponse { 35 | NSData* _data; 36 | BOOL _done; 37 | } 38 | 39 | @dynamic contentType; 40 | 41 | + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type { 42 | return [[[self class] alloc] initWithData:data contentType:type]; 43 | } 44 | 45 | - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type { 46 | if ((self = [super init])) { 47 | _data = data; 48 | 49 | self.contentType = type; 50 | self.contentLength = data.length; 51 | } 52 | return self; 53 | } 54 | 55 | - (NSData*)readData:(NSError**)error { 56 | NSData* data; 57 | if (_done) { 58 | data = [NSData data]; 59 | } else { 60 | data = _data; 61 | _done = YES; 62 | } 63 | return data; 64 | } 65 | 66 | - (NSString*)description { 67 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 68 | [description appendString:@"\n\n"]; 69 | [description appendString:GCDWebServerDescribeData(_data, self.contentType)]; 70 | return description; 71 | } 72 | 73 | @end 74 | 75 | @implementation GCDWebServerDataResponse (Extensions) 76 | 77 | + (instancetype)responseWithText:(NSString*)text { 78 | return [[self alloc] initWithText:text]; 79 | } 80 | 81 | + (instancetype)responseWithHTML:(NSString*)html { 82 | return [[self alloc] initWithHTML:html]; 83 | } 84 | 85 | + (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { 86 | return [[self alloc] initWithHTMLTemplate:path variables:variables]; 87 | } 88 | 89 | + (instancetype)responseWithJSONObject:(id)object { 90 | return [[self alloc] initWithJSONObject:object]; 91 | } 92 | 93 | + (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type { 94 | return [[self alloc] initWithJSONObject:object contentType:type]; 95 | } 96 | 97 | + (instancetype)responseWithText:(NSString *)object contentType:(NSString*)type { 98 | return [[self alloc] initWithText:object contentType:type]; 99 | } 100 | 101 | - (instancetype)initWithText:(NSString*)text { 102 | NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding]; 103 | if (data == nil) { 104 | GWS_DNOT_REACHED(); 105 | return nil; 106 | } 107 | return [self initWithData:data contentType:@"text/plain; charset=utf-8"]; 108 | } 109 | 110 | - (instancetype)initWithText:(NSString*)text contentType:(NSString*)type { 111 | NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding]; 112 | if (data == nil) { 113 | GWS_DNOT_REACHED(); 114 | return nil; 115 | } 116 | return [self initWithData:data contentType:type]; 117 | } 118 | 119 | - (instancetype)initWithHTML:(NSString*)html { 120 | NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding]; 121 | if (data == nil) { 122 | GWS_DNOT_REACHED(); 123 | return nil; 124 | } 125 | return [self initWithData:data contentType:@"text/html; charset=utf-8"]; 126 | } 127 | 128 | - (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { 129 | NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; 130 | [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) { 131 | [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)]; 132 | }]; 133 | return [self initWithHTML:html]; 134 | } 135 | 136 | - (instancetype)initWithJSONObject:(id)object { 137 | return [self initWithJSONObject:object contentType:@"application/json"]; 138 | } 139 | 140 | - (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type { 141 | NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL]; 142 | if (data == nil) { 143 | GWS_DNOT_REACHED(); 144 | return nil; 145 | } 146 | return [self initWithData:data contentType:type]; 147 | } 148 | 149 | @end 150 | -------------------------------------------------------------------------------- /AppHost/ThirdParty/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2015, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 29 | // http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 30 | 31 | #import 32 | 33 | /** 34 | * Convenience constants for "informational" HTTP status codes. 35 | */ 36 | typedef NS_ENUM(NSInteger, GCDWebServerInformationalHTTPStatusCode) { 37 | kGCDWebServerHTTPStatusCode_Continue = 100, 38 | kGCDWebServerHTTPStatusCode_SwitchingProtocols = 101, 39 | kGCDWebServerHTTPStatusCode_Processing = 102 40 | }; 41 | 42 | /** 43 | * Convenience constants for "successful" HTTP status codes. 44 | */ 45 | typedef NS_ENUM(NSInteger, GCDWebServerSuccessfulHTTPStatusCode) { 46 | kGCDWebServerHTTPStatusCode_OK = 200, 47 | kGCDWebServerHTTPStatusCode_Created = 201, 48 | kGCDWebServerHTTPStatusCode_Accepted = 202, 49 | kGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203, 50 | kGCDWebServerHTTPStatusCode_NoContent = 204, 51 | kGCDWebServerHTTPStatusCode_ResetContent = 205, 52 | kGCDWebServerHTTPStatusCode_PartialContent = 206, 53 | kGCDWebServerHTTPStatusCode_MultiStatus = 207, 54 | kGCDWebServerHTTPStatusCode_AlreadyReported = 208 55 | }; 56 | 57 | /** 58 | * Convenience constants for "redirection" HTTP status codes. 59 | */ 60 | typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) { 61 | kGCDWebServerHTTPStatusCode_MultipleChoices = 300, 62 | kGCDWebServerHTTPStatusCode_MovedPermanently = 301, 63 | kGCDWebServerHTTPStatusCode_Found = 302, 64 | kGCDWebServerHTTPStatusCode_SeeOther = 303, 65 | kGCDWebServerHTTPStatusCode_NotModified = 304, 66 | kGCDWebServerHTTPStatusCode_UseProxy = 305, 67 | kGCDWebServerHTTPStatusCode_TemporaryRedirect = 307, 68 | kGCDWebServerHTTPStatusCode_PermanentRedirect = 308 69 | }; 70 | 71 | /** 72 | * Convenience constants for "client error" HTTP status codes. 73 | */ 74 | typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) { 75 | kGCDWebServerHTTPStatusCode_BadRequest = 400, 76 | kGCDWebServerHTTPStatusCode_Unauthorized = 401, 77 | kGCDWebServerHTTPStatusCode_PaymentRequired = 402, 78 | kGCDWebServerHTTPStatusCode_Forbidden = 403, 79 | kGCDWebServerHTTPStatusCode_NotFound = 404, 80 | kGCDWebServerHTTPStatusCode_MethodNotAllowed = 405, 81 | kGCDWebServerHTTPStatusCode_NotAcceptable = 406, 82 | kGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407, 83 | kGCDWebServerHTTPStatusCode_RequestTimeout = 408, 84 | kGCDWebServerHTTPStatusCode_Conflict = 409, 85 | kGCDWebServerHTTPStatusCode_Gone = 410, 86 | kGCDWebServerHTTPStatusCode_LengthRequired = 411, 87 | kGCDWebServerHTTPStatusCode_PreconditionFailed = 412, 88 | kGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413, 89 | kGCDWebServerHTTPStatusCode_RequestURITooLong = 414, 90 | kGCDWebServerHTTPStatusCode_UnsupportedMediaType = 415, 91 | kGCDWebServerHTTPStatusCode_RequestedRangeNotSatisfiable = 416, 92 | kGCDWebServerHTTPStatusCode_ExpectationFailed = 417, 93 | kGCDWebServerHTTPStatusCode_UnprocessableEntity = 422, 94 | kGCDWebServerHTTPStatusCode_Locked = 423, 95 | kGCDWebServerHTTPStatusCode_FailedDependency = 424, 96 | kGCDWebServerHTTPStatusCode_UpgradeRequired = 426, 97 | kGCDWebServerHTTPStatusCode_PreconditionRequired = 428, 98 | kGCDWebServerHTTPStatusCode_TooManyRequests = 429, 99 | kGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431 100 | }; 101 | 102 | /** 103 | * Convenience constants for "server error" HTTP status codes. 104 | */ 105 | typedef NS_ENUM(NSInteger, GCDWebServerServerErrorHTTPStatusCode) { 106 | kGCDWebServerHTTPStatusCode_InternalServerError = 500, 107 | kGCDWebServerHTTPStatusCode_NotImplemented = 501, 108 | kGCDWebServerHTTPStatusCode_BadGateway = 502, 109 | kGCDWebServerHTTPStatusCode_ServiceUnavailable = 503, 110 | kGCDWebServerHTTPStatusCode_GatewayTimeout = 504, 111 | kGCDWebServerHTTPStatusCode_HTTPVersionNotSupported = 505, 112 | kGCDWebServerHTTPStatusCode_InsufficientStorage = 507, 113 | kGCDWebServerHTTPStatusCode_LoopDetected = 508, 114 | kGCDWebServerHTTPStatusCode_NotExtended = 510, 115 | kGCDWebServerHTTPStatusCode_NetworkAuthenticationRequired = 511 116 | }; 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![AppHost](https://upload-images.jianshu.io/upload_images/277783-768ecdd81b026a44.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 2 | 3 | AppHost 是一整体解决 H5 和 native 协作开发的框架和服务。试图解决 native 和 H5 目前开发质量低下、业务膨胀后代码混乱、两端联调困难等,彼此割裂的现状。 4 | 作为一种 JSBridge 的实现方法,AppHost 像一座桥,将 native 和 H5 开发打通,一边是提供设计良好的 native framework 和相关 protocol ,提高 native 接口的交付能力和开发质量;一边是为 H5 开发的页面和 native 联调,提供大量辅助调试工具和基本性能调优工具,让前端开发者对 H5 in App 的调试体验像调试原生浏览器一样,提升质量和开发效率。 5 | ## native 开发用例 6 | 7 | ### 1.基本加载 H5 页面 8 | ```objective-c 9 | AppHostViewController *appHost = [[AppHostViewController alloc] init]; 10 | appHost.url = @"https://m.you.163.com"; 11 | appHost.pageTitle = @"好的生活没那么贵"; 12 | appHost.rightActionBarTitle = @"点赞";// 右上角按钮文案 13 | 14 | [self.navigationController pushViewController:appHost animated:YES]; 15 | ``` 16 | ### 2.用增强后的 AppHostViewController 加载 H5 页面 17 | WebViewViewController 继承自AppHostViewController,自定义拦截`openapp.jdmobile:`协议和自定义了 HUD 行为,详见 [AppHostExample](https://github.com/hite/AppHostExample)源码。 18 | ```objective-c 19 | WebViewViewController *vc = [[WebViewViewController alloc] init]; 20 | NSDictionary *object = self.objects[indexPath.row]; 21 | NSString *url = [object objectForKey:@"url"]; 22 | NSString *fileName = [object objectForKey:@"fileName"]; 23 | if (url) { 24 | vc.url = url; 25 | } else if(fileName.length > 0){ 26 | NSString *dir = [object objectForKey:@"dir"]; 27 | NSURL * _Nonnull mainURL = [[NSBundle mainBundle] bundleURL]; 28 | NSString* domain = [object objectForKey:@"domain"]; 29 | if (dir.length > 0) { 30 | NSURL *url = [mainURL URLByAppendingPathComponent:dir]; 31 | [vc loadIndexFile:fileName inDirectory:url domain:domain]; 32 | } else { 33 | [vc loadLocalFile:[mainURL URLByAppendingPathComponent:fileName] domain:domain]; 34 | } 35 | } 36 | 37 | [self.navigationController pushViewController:vc animated:YES]; 38 | ``` 39 | ### 3.自定义 Response,新增 h5 接口 40 | 详见 [AppHostExample](https://github.com/hite/AppHostExample)源码。 41 | ```objective-c 42 | // HUDResponse.h 43 | #import 44 | 45 | NS_ASSUME_NONNULL_BEGIN 46 | @interface HUDResponse : AppHostResponse 47 | 48 | @end 49 | NS_ASSUME_NONNULL_END 50 | // HUDResponse.m 51 | + (NSDictionary *)supportActionList 52 | { 53 | return @{ 54 | @"hideLoading":@"1" 55 | }; 56 | } 57 | 58 | #pragma mark - override 59 | ah_doc_begin(hideLoading, "隐藏 loading 的 HUD 动画,UIView+Toast实现。") 60 | ah_doc_code(window.appHost.invoke("hideLoading")) 61 | ah_doc_code_expect("在有 loading 动画的情况下,调用此接口,会隐藏 loading。") 62 | ah_doc_end 63 | - (void)hideLoading 64 | { 65 | [self.appHost.view hideToastActivity]; 66 | } 67 | ``` 68 | ## Remote Debugger 演示 69 | ### 1.如何打开远程调试功能 70 | 工程代码运行之后,按照 XCode 日志里的提示(或者点击 App 里右上角一个 AH 样的图标,展开后的日志了有 url,长按复制或者在浏览器输入),用电脑浏览器打开调试页面,展现的就是调试 Remote Debugger 的 Console界面。 71 | ![Debugger 整体使用](https://upload-images.jianshu.io/upload_images/277783-e520ecf4d92e53da.gif?imageMogr2/auto-orient/strip) 72 | 73 | ## AppHost 的功能总览 74 | ![功能总览](https://upload-images.jianshu.io/upload_images/277783-d30643fad6c62bbd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 75 | 76 | ## 如何安装 77 | 介绍两种方式,作为动态链接库 framework 或者以子项目的方式引入。 78 | #### 1. 动态链接库framework 79 | - 打开`AppHost.xcodeproj`工程 80 | - 选择 scheme 如图 ![分架构的 framework build 脚本](https://upload-images.jianshu.io/upload_images/277783-6144027c6b7af2d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 81 | - 运行后会自动打开一个文件夹,选择你需要的架构(模拟器或者 device)将`AppHost.framework` 托到桌面(任何一个容易找到的地方)。 82 | - 接着,选择工程 target -> general 面板下面的 `Embedded Binaries` ,点击 + 号 83 | - 选择`add others...`,选中刚刚 build 好的`AppHost.framework`,添加过程中,需要选择`copy items if needed`选项。(后续你可以把这个文件放置到工程目录下任意地方,然后 add 到 Xcode 工程里) 84 | - 完毕,在工程里即可使用`#import ` 85 | #### 2.Embedded Framework 86 | - 切换到工程的根目录下,运行下面命令, 添加 AppHost 作为 git [submodule](https://git-scm.com/docs/git-submodule) 87 | ```bash 88 | $ git submodule add https://github.com/hite/AppHost.git 89 | ``` 90 | 91 | - 打开 `AppHost` 文件夹, 把 `AppHost.xcodeproj` 拖到 Project Navigator tab,你的项目Xcode project 根目录下 92 | 93 | > AppHost.xcodeproj 应该在你的工程文件蓝色图标的下方,处于可打开状态,不能打开说明你单独打开了 AppHost.xcodeproj,请关闭 94 | 95 | - 设置主工程和子工程的 deploy target 一致. 96 | - Next, 在 Project Navigator 里选择你的项目(蓝色图标),切换到 target configuration 窗口,在 "Targets" 窗口下,选择应用 target 97 | - 在窗口顶部上面,选择 "General" 窗口. 98 | - "Embedded Binaries" 区域点击 + 号. 99 | - 在弹出的选择窗口里,下拉到`Products` 文件夹,选择`AppHost.framework` 100 | > `AppHost.framework` 会自动添加为 target 依赖。在`build phase\copy files` 的` linked framework` 和 `embedded framework` 也会自动添加`AppHost.framework`,这两个地方是为了能在模拟器和真机上运行 101 | - 完毕,在工程里即可使用`#import ` 102 | ## H5 端使用示例 103 | 暴露给 h5 的数据有两类,一类是 apphost 的静态属性;一类是接口; 104 | #### > AppHost 静态属性,包含属性有 105 | 1. appInfo 106 | 2. supportFunctionType 107 | 这两个静态属性可以在 h5 的任意地方调用都是可用的,调用举例, 108 | ```javascript 109 | // 获取当前是否是 iPhone X 设备(iOS only) 110 | var name = appHost.appInfo.iPhoneXInfo 111 | // 获取当前 App 是否支持此某个接口,如 `oepnFinancial`, 112 | if(apphost.supportFunctionType && parseInt(apphost.supportFunctionType.openFinancial, 10) >0){ 113 | //支持打开理财界面 114 | } 115 | ``` 116 | #### > AppHost 的核心接口 117 | 大部分核心接口需要在 'onready', 内调用,但是如果是一些和 UI 无关的接口,可以在任意地方调用,如 统计接口,`appHost.invoke('log',{})` 118 | ```javascript 119 | window.appHost.on('onready',function(data){ 120 | window.appHost.invoke('sendLogToES', {'content':'XXX' }) 121 | // your code go here. 122 | }); 123 | ``` 124 | **第一个核心接口,`invoke`, 即 `window.appHost.invoke`**,它是 h5 调用 native 的唯一入口,可调用接口和调用用例可以使用 `Remote Debugger`的 console 来查看; 125 | 126 | **第二个核心接口,`on`,即`window.appHost.on`**,它是 h5 接收 native 调用的一种方式,用 on 来接收 native 调用比较适合在 delegate 模式; 127 | `window.appHost.invoke` 也是可以接收 native 回调的,也就是在 invoke 的最后一个参数传入一个 function,如 128 | ``` 129 | window.appHost.invoke('alert', {'text': 'text'}); 130 | //如果是callback,支持如下语法 131 | window.appHost.invoke('alert', {'text': '点击确定后,有回调'},function(){ 132 | appHost.invoke('toast',{'text':'你点击了alet的确定按钮'}); 133 | }); 134 | }); 135 | ``` 136 | 137 | 更多用例请查看 [AppHostExample](https://github.com/hite/AppHostExample) 源码 138 | -------------------------------------------------------------------------------- /AppHost/Core/Response/AHResponseManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // AHResponseManager.m 3 | // AppHost 4 | // 5 | // Created by liang on 2019/1/22. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AHResponseManager.h" 10 | #import 11 | #import "AHDebugResponse.h" 12 | 13 | @interface AHResponseManager () 14 | 15 | // 以下是注册 response 使用的属性 16 | 17 | /** 18 | 自定义response类 19 | */ 20 | @property (nonatomic, strong, readwrite) NSMutableArray *customResponseClasses; 21 | /** 22 | response类的 实例的缓存。 23 | */ 24 | @property (nonatomic, strong) NSMutableDictionary *responseClassObjs; 25 | 26 | @end 27 | 28 | @implementation AHResponseManager 29 | 30 | +(instancetype)defaultManager 31 | { 32 | static dispatch_once_t onceToken; 33 | static AHResponseManager *kResponeManger = nil; 34 | dispatch_once(&onceToken, ^{ 35 | kResponeManger = [AHResponseManager new]; 36 | 37 | kResponeManger.responseClassObjs = [NSMutableDictionary dictionaryWithCapacity:10]; 38 | kResponeManger.customResponseClasses = [NSMutableArray arrayWithCapacity:10]; 39 | 40 | // 静态注册 可响应的类 41 | NSArray *responseClassNames = @[ 42 | @"AHNavigationResponse", 43 | @"AHNavigationBarResponse", 44 | @"AHBuiltInResponse", 45 | #ifdef AH_DEBUG 46 | @"AHDebugResponse", 47 | #endif 48 | @"AHAppLoggerResponse"]; 49 | [responseClassNames enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 50 | [kResponeManger.customResponseClasses addObject:NSClassFromString(obj)]; 51 | }]; 52 | 53 | //TODO 54 | #ifdef AH_DEBUG 55 | NSString *docsdir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 56 | NSString *file = [docsdir stringByAppendingPathComponent:kAppHostTestCaseFileName]; 57 | 58 | if ([[NSFileManager defaultManager] fileExistsAtPath:file]) { 59 | NSError *err = nil; 60 | [[NSFileManager defaultManager] removeItemAtPath:file error:&err]; 61 | if (err) { 62 | AHLog(@"删除文件错误"); 63 | } 64 | } 65 | #endif 66 | }); 67 | 68 | return kResponeManger; 69 | } 70 | 71 | #pragma mark - public 72 | - (NSString *)actionSignature:(NSString *)action withParam:(BOOL)hasParamDict withCallback:(BOOL)hasCallback 73 | { 74 | return [NSString stringWithFormat:@"%@%@%@", action, (hasParamDict?@"_":@""), (hasCallback?@"$":@"")]; 75 | } 76 | 77 | - (void)addCustomResponse:(Class)cls 78 | { 79 | if (cls) { 80 | [self.customResponseClasses addObject:cls]; 81 | } 82 | } 83 | 84 | - (id)responseForActionSignature:(NSString *)signature withAppHost:(AppHostViewController * _Nonnull)appHost 85 | { 86 | if (self.customResponseClasses.count == 0) { 87 | return nil; 88 | } 89 | 90 | id vc = nil; 91 | // 逆序遍历,让后添加的 Response 能够覆盖内置的方法; 92 | for (NSInteger i = self.customResponseClasses.count - 1; i >= 0; i--) { 93 | Class responseClass = [self.customResponseClasses objectAtIndex:i]; 94 | if ([responseClass isSupportedActionSignature:signature]) { 95 | // 先判断是否可以响应,再决定初始化。 96 | if (appHost) { 97 | NSString *key = NSStringFromClass(responseClass); 98 | if (vc == nil) { 99 | vc = [self.responseClassObjs objectForKey:key]; 100 | vc = [[responseClass alloc] initWithAppHost:appHost]; 101 | // 缓存住 102 | [self.responseClassObjs setObject:vc forKey:key]; 103 | } 104 | } else { 105 | vc = [responseClass new]; 106 | } 107 | 108 | break; 109 | } 110 | } 111 | 112 | return vc; 113 | } 114 | 115 | - (Class)responseForActionSignature:(NSString *)action 116 | { 117 | // 逆序遍历,让后添加的 Response 能够覆盖内置的方法; 118 | Class r = nil; 119 | for (NSInteger i = self.customResponseClasses.count - 1; i >= 0; i--) { 120 | Class responseClass = [self.customResponseClasses objectAtIndex:i]; 121 | if ([responseClass isSupportedActionSignature:action]) { 122 | r = responseClass; 123 | break; 124 | } 125 | } 126 | 127 | return r; 128 | } 129 | 130 | #ifdef AH_DEBUG 131 | 132 | /** 133 | //TODO: 缓存 134 | */ 135 | static NSDictionary *kAllResponseMethods = nil; 136 | static pthread_mutex_t lock; 137 | - (NSDictionary *)allResponseMethods 138 | { 139 | pthread_mutex_init(&lock, NULL); 140 | pthread_mutex_lock(&lock); 141 | if (kAllResponseMethods) { 142 | pthread_mutex_unlock(&lock); 143 | return kAllResponseMethods; 144 | } 145 | 146 | kAllResponseMethods = [NSMutableDictionary dictionaryWithCapacity:10]; 147 | // 148 | for (NSInteger i = 0; i < self.customResponseClasses.count; i++) { 149 | Class responseClass = [self.customResponseClasses objectAtIndex:i]; 150 | NSMutableArray *methods = [NSMutableArray arrayWithCapacity:10]; 151 | [[responseClass supportActionList] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) { 152 | if ([obj integerValue] > 0) { 153 | [methods addObject:key]; 154 | } 155 | }]; 156 | 157 | if (methods.count > 0) { 158 | [kAllResponseMethods setValue:methods forKey:NSStringFromClass(responseClass)]; 159 | } 160 | } 161 | 162 | pthread_mutex_unlock(&lock); 163 | return kAllResponseMethods; 164 | } 165 | 166 | #endif 167 | 168 | -(void)dealloc 169 | { 170 | // 清理 response 171 | [self.responseClassObjs enumerateKeysAndObjectsUsingBlock:^(NSString *key, id _Nonnull obj, BOOL *_Nonnull stop) { 172 | obj = nil; 173 | }]; 174 | [self.responseClassObjs removeAllObjects]; 175 | self.responseClassObjs = nil; 176 | 177 | } 178 | @end 179 | --------------------------------------------------------------------------------