261 lines
11 KiB
Dart
261 lines
11 KiB
Dart
//
|
||
// FATWebView.m
|
||
// FinApplet
|
||
//
|
||
// Created by Haley on 2019/12/9.
|
||
// Copyright © 2019 finogeeks. All rights reserved.
|
||
//
|
||
|
||
#import "FATWebView.h"
|
||
|
||
#import <FinApplet/FinApplet.h>
|
||
#import <WebKit/WebKit.h>
|
||
#import "FATExtUtil.h"
|
||
|
||
@interface FATWebView () <WKScriptMessageHandler, WKUIDelegate, WKNavigationDelegate, UIScrollViewDelegate>
|
||
|
||
@property (nonatomic, strong) WKWebView *webView;
|
||
|
||
@property (nonatomic, strong) UIProgressView *progressView;
|
||
|
||
@property (nonatomic, copy) NSString *appletId;
|
||
|
||
@end
|
||
|
||
@implementation FATWebView
|
||
|
||
- (instancetype)initWithFrame:(CGRect)frame URL:(NSURL *)URL appletId:(NSString *)appletId {
|
||
self = [super initWithFrame:frame];
|
||
if (self) {
|
||
_appletId = appletId;
|
||
[self p_initSubViews:URL];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (void)layoutSubviews {
|
||
[super layoutSubviews];
|
||
|
||
self.webView.frame = self.bounds;
|
||
|
||
CGFloat y = fabs(self.webView.scrollView.contentOffset.y);
|
||
self.progressView.frame = CGRectMake(0, y, self.bounds.size.width, 4);
|
||
}
|
||
|
||
- (void)dealloc {
|
||
[self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
|
||
}
|
||
|
||
#pragma mark - private method
|
||
|
||
- (void)p_initSubViews:(NSURL *)URL {
|
||
FATWeakScriptMessageDelegate *scriptMessageDelegate = [FATWeakScriptMessageDelegate new];
|
||
scriptMessageDelegate.scriptDelegate = self;
|
||
|
||
WKUserContentController *userContentController = [WKUserContentController new];
|
||
NSString *souce = @"window.__fcjs_environment='miniprogram'";
|
||
WKUserScript *script = [[WKUserScript alloc] initWithSource:souce injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:true];
|
||
[userContentController addUserScript:script];
|
||
[userContentController addScriptMessageHandler:scriptMessageDelegate name:@"webInvokeHandler"];
|
||
[userContentController addScriptMessageHandler:scriptMessageDelegate name:@"webPublishHandler"];
|
||
|
||
WKWebViewConfiguration *wkWebViewConfiguration = [WKWebViewConfiguration new];
|
||
wkWebViewConfiguration.allowsInlineMediaPlayback = YES;
|
||
wkWebViewConfiguration.userContentController = userContentController;
|
||
|
||
self.webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:wkWebViewConfiguration];
|
||
self.webView.UIDelegate = self;
|
||
self.webView.navigationDelegate = self;
|
||
self.webView.clipsToBounds = YES;
|
||
self.webView.scrollView.delegate = self;
|
||
[self addSubview:self.webView];
|
||
|
||
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestReloadRevalidatingCacheData timeoutInterval:60];
|
||
[self.webView loadRequest:request];
|
||
|
||
self.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 4)];
|
||
self.progressView.progressTintColor = [UIColor colorWithRed:44 / 255.0 green:127 / 255.0 blue:251 / 255.0 alpha:1];
|
||
[self addSubview:self.progressView];
|
||
|
||
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
|
||
|
||
NSString *version = [FATClient sharedClient].version;
|
||
NSString *appendUserAgent;
|
||
NSString *model = [[UIDevice currentDevice] model];
|
||
if ([FATExtUtil currentProductIdentificationIsEmpty]) {
|
||
appendUserAgent = [NSString stringWithFormat:@"Provider/finogeeks (%@; miniprogram; FinChat; runtimeSdkVersion/%@)", model, version];
|
||
} else {
|
||
appendUserAgent = [NSString stringWithFormat:@"Provider/%@ (%@; miniprogram; %@; runtimeSdkVersion/%@)", [FATExtUtil currentProductIdentification], model, [FATExtUtil currentProductIdentification], version];
|
||
}
|
||
NSString *customUA = [FATClient sharedClient].uiConfig.appendingCustomUserAgent;
|
||
if (customUA.length > 0) {
|
||
appendUserAgent = [appendUserAgent stringByAppendingString:@" "];
|
||
appendUserAgent = [appendUserAgent stringByAppendingString:customUA];
|
||
}
|
||
|
||
__weak typeof(self) weakSelf = self;
|
||
[self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError *_Nullable error) {
|
||
NSString *userAgent = result;
|
||
userAgent = [userAgent stringByAppendingFormat:@" %@", appendUserAgent];
|
||
weakSelf.webView.customUserAgent = userAgent;
|
||
}];
|
||
}
|
||
|
||
- (void)callJS:(NSString *)js callback:(void (^)(id result, NSError *error))callback {
|
||
[self.webView evaluateJavaScript:js completionHandler:^(id _Nullable result, NSError *_Nullable error) {
|
||
if (callback) {
|
||
callback(result, error);
|
||
}
|
||
}];
|
||
}
|
||
|
||
- (void)webInvokeHandler:(NSDictionary *)data {
|
||
if (!data) {
|
||
return;
|
||
}
|
||
|
||
NSString *command = data[@"C"];
|
||
NSString *paramsString = data[@"paramsString"];
|
||
|
||
// 可能是字符串,也可能是number
|
||
id callbackId = data[@"callbackId"];
|
||
|
||
if (!command) {
|
||
return;
|
||
}
|
||
|
||
if ([command isEqualToString:@"initPage"]) {
|
||
unsigned long long webPageId = self.webView.hash;
|
||
NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, @(webPageId)];
|
||
[self callJS:js callback:nil];
|
||
return;
|
||
}
|
||
|
||
// 执行注入的事件
|
||
NSDictionary<NSString *, FATWebExtensionApiHandlerModel *> *webExtensionApis = [FATWebExtension webExtensionApis];
|
||
id handler = (__bridge id)(webExtensionApis[command].isOld ? webExtensionApis[command].deprecatedHandler : webExtensionApis[command].handler);
|
||
if (handler) {
|
||
FATExtensionApiCallback callbck = ^void(FATExtensionCode code, NSDictionary<NSString *, NSObject *> *result) {
|
||
NSString *successErrMsg = [NSString stringWithFormat:@"%@:ok", command];
|
||
NSString *failErrMsg = [NSString stringWithFormat:@"%@:fail", command];
|
||
NSString *cancelErrMsg = [NSString stringWithFormat:@"%@:cancel", command];
|
||
|
||
NSString *errMsg = (NSString *)result[@"errMsg"];
|
||
if (errMsg && [errMsg isKindOfClass:[NSString class]] && errMsg.length > 0) {
|
||
successErrMsg = [successErrMsg stringByAppendingFormat:@" %@", errMsg];
|
||
failErrMsg = [failErrMsg stringByAppendingFormat:@" %@", errMsg];
|
||
cancelErrMsg = [cancelErrMsg stringByAppendingFormat:@" %@", errMsg];
|
||
}
|
||
|
||
switch (code) {
|
||
case FATExtensionCodeSuccess: {
|
||
NSMutableDictionary *successResult = [NSMutableDictionary dictionaryWithDictionary:result];
|
||
[successResult setObject:successErrMsg forKey:@"errMsg"];
|
||
NSString *resultJsonString = [self fat_jsonStringFromDict:successResult];
|
||
|
||
NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, resultJsonString];
|
||
[self callJS:js callback:nil];
|
||
break;
|
||
}
|
||
case FATExtensionCodeCancel: {
|
||
NSMutableDictionary *cancelResult = [NSMutableDictionary dictionaryWithDictionary:result];
|
||
[cancelResult setObject:cancelErrMsg forKey:@"errMsg"];
|
||
NSString *resultJsonString = [self fat_jsonStringFromDict:cancelResult];
|
||
|
||
NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, resultJsonString];
|
||
[self callJS:js callback:nil];
|
||
break;
|
||
}
|
||
case FATExtensionCodeFailure: {
|
||
NSMutableDictionary *failResult = [NSMutableDictionary dictionaryWithDictionary:result];
|
||
[failResult setObject:failErrMsg forKey:@"errMsg"];
|
||
NSString *resultJsonString = [self fat_jsonStringFromDict:failResult];
|
||
|
||
NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, resultJsonString];
|
||
[self callJS:js callback:nil];
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
};
|
||
NSDictionary *param = [self fat_jsonObjectFromString:paramsString];
|
||
if (webExtensionApis[command].isOld) {
|
||
webExtensionApis[command].deprecatedHandler(param, callbck);
|
||
} else {
|
||
FATAppletInfo *appletInfo = [[FATClient sharedClient] getAppletInfo:self.appletId];
|
||
webExtensionApis[command].handler(appletInfo, param, callbck);
|
||
}
|
||
return;
|
||
}
|
||
|
||
NSMutableDictionary *failResult = [NSMutableDictionary dictionary];
|
||
NSString *failErrMsg = [NSString stringWithFormat:@"%@:fail 该api未实现", command];
|
||
[failResult setObject:failErrMsg forKey:@"errMsg"];
|
||
NSString *resultJsonString = [self fat_jsonStringFromDict:failResult];
|
||
|
||
NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, resultJsonString];
|
||
[self callJS:js callback:nil];
|
||
}
|
||
|
||
- (void)webPublishHandler:(NSDictionary *)data {
|
||
}
|
||
|
||
#pragma mark - KVO
|
||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
|
||
if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
|
||
[self.progressView setAlpha:1.0f];
|
||
[self.progressView setProgress:self.webView.estimatedProgress animated:YES];
|
||
if (self.webView.estimatedProgress >= 1.0f) {
|
||
[UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{
|
||
[self.progressView setAlpha:0.0f];
|
||
} completion:^(BOOL finished) {
|
||
[self.progressView setProgress:0.0f animated:YES];
|
||
}];
|
||
}
|
||
} else {
|
||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||
}
|
||
}
|
||
|
||
//MARK: - TOOL METHOD
|
||
- (NSString *)fat_jsonStringFromDict:(NSDictionary *)dict {
|
||
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
|
||
if (!data) {
|
||
return nil;
|
||
}
|
||
|
||
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||
return jsonString;
|
||
}
|
||
|
||
- (id)fat_jsonObjectFromString:(NSString *)string {
|
||
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
|
||
|
||
if (!data) {
|
||
return nil;
|
||
}
|
||
|
||
id object = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
|
||
return object;
|
||
}
|
||
|
||
//MARK: - WKScriptMessageHandler
|
||
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
|
||
NSString *name = message.name;
|
||
id body = message.body;
|
||
|
||
if ([name isEqualToString:@"webInvokeHandler"]) {
|
||
[self webInvokeHandler:body];
|
||
} else if ([name isEqualToString:@"webPublishHandler"]) {
|
||
[self webPublishHandler:body];
|
||
}
|
||
}
|
||
|
||
//MARK: - WKNavigationDelegate
|
||
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
|
||
decisionHandler(WKNavigationActionPolicyAllow);
|
||
}
|
||
|
||
@end
|