mop-flutter-sdk/ios/Classes/FinAppletExt/View/FATWebView.m

261 lines
11 KiB
Dart
Raw Normal View History

2023-12-06 14:56:29 +08:00
//
// 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