// // MSRoomGameWebVC.m // YuMi // // Created by duoban on 2024/4/28. // #import "MSRoomGameWebVC.h" #import "LittleGameInfoModel.h" #import "RoomInfoModel.h" #import "XPIAPRechargeViewController.h" @interface MSWeakWebViewScriptMessageDelegate : NSObject //WKScriptMessageHandler 这个协议类专门用来处理JavaScript调用原生OC的方法 @property (nonatomic, weak) id scriptDelegate; - (instancetype)initWithDelegate:(id)scriptDelegate; @end @implementation MSWeakWebViewScriptMessageDelegate - (instancetype)initWithDelegate:(id)scriptDelegate { self = [super init]; if (self) { _scriptDelegate = scriptDelegate; } return self; } //遵循WKScriptMessageHandler协议,必须实现如下方法,然后把方法向外传递 //通过接收JS传出消息的name进行捕捉的回调方法 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([self.scriptDelegate respondsToSelector:@selector(userContentController:didReceiveScriptMessage:)]) { [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message]; } } @end typedef NS_ENUM(NSInteger, MSGameType) { MSGameTypeBaiShun, // 百顺游戏 MSGameTypeJoyPlay, // JoyPlay游戏 MSGameTypeCC // CC游戏 }; NSString * const kMSGetConfig = @"getConfig"; NSString * const kMSDestroy = @"destroy"; NSString * const kMSGameRecharge = @"gameRecharge"; NSString * const kMSGameLoaded = @"gameLoaded"; NSString * const kJPRecharge = @"recharge"; NSString * const kJPClickRecharge = @"clickRecharge"; NSString * const kJPClose = @"newTppClose"; @interface MSRoomGameWebVC () @property (strong, nonatomic) WKWebView *webView; @property (strong, nonatomic) UIProgressView *progressView; @property (nonatomic, strong) WKUserContentController *ms_userContentController; @property (nonatomic,weak) id hostDelegate; @property(nonatomic,strong) ActivityInfoModel *gameModel; @property(nonatomic,strong) UIButton *backBtn; @property (nonatomic, assign) MSGameType gameType; @property (nonatomic, assign) BOOL isCharing; @property (nonatomic, strong) NSURLRequest *lastRequest; @property (nonatomic, assign) NSInteger retryCount; @property (nonatomic, strong) NSTimer *loadingTimer; @end @implementation MSRoomGameWebVC - (void)dealloc { [self cleanupWebView]; [self hideHUD]; [self.loadingTimer invalidate]; self.loadingTimer = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)cleanupWebView { if (_webView) { [_webView stopLoading]; _webView.navigationDelegate = nil; _webView.UIDelegate = nil; [_webView removeFromSuperview]; _webView = nil; } if (_ms_userContentController) { [_ms_userContentController removeScriptMessageHandlerForName:kMSGetConfig]; [_ms_userContentController removeScriptMessageHandlerForName:kMSDestroy]; [_ms_userContentController removeScriptMessageHandlerForName:kMSGameRecharge]; [_ms_userContentController removeScriptMessageHandlerForName:kMSGameLoaded]; [_ms_userContentController removeScriptMessageHandlerForName:@"closeGame"]; [_ms_userContentController removeScriptMessageHandlerForName:@"pay"]; [_ms_userContentController removeScriptMessageHandlerForName:kJPClose]; [_ms_userContentController removeScriptMessageHandlerForName:kJPRecharge]; [_ms_userContentController removeScriptMessageHandlerForName:kJPClickRecharge]; _ms_userContentController = nil; } } - (instancetype)initWithDelegate:(id)delegate gameModel:(ActivityInfoModel *)gameModel { self = [super init]; if (self) { self.hostDelegate = delegate; self.gameModel = gameModel; NSString *upperCode = gameModel.code.uppercaseString; if ([upperCode isEqualToString:@"BAISHUN"]) { self.gameType = MSGameTypeBaiShun; } else if ([upperCode isEqualToString:@"JOYPLAY"]) { self.gameType = MSGameTypeJoyPlay; } else { self.gameType = MSGameTypeCC; } } return self; } - (BOOL)isHiddenNavBar { return YES; } - (void)viewDidLoad { [super viewDidLoad]; [self installUI]; [self setupBackButton]; #if DEBUG // [self setupLoadingTimer]; #endif [self setupNotifications]; [self showLoading]; } - (void)setupLoadingTimer { self.loadingTimer = [NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:@selector(handleLoadingTimeout) userInfo:nil repeats:NO]; } - (void)setupNotifications { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleApplicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleApplicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil]; } - (void)handleLoadingTimeout { [self hideHUD]; [XNDJTDDLoadingTool showErrorWithMessage:@"加载超时,请检查网络后重试"]; } - (void)handleApplicationDidBecomeActive { if (self.webView && !self.webView.isLoading) { [self.webView reload]; } } - (void)handleApplicationWillResignActive { [self.webView evaluateJavaScript:@"if(typeof(onAppPause) === 'function') { onAppPause(); }" completionHandler:nil]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (self.gameType != MSGameTypeBaiShun && self.isCharing) { self.isCharing = NO; [self updateCoin]; } } - (void)setupBackButton { if (self.gameType == MSGameTypeJoyPlay && self.gameModel.showType == ActivityShowType_Full) { return; } self.backBtn = [UIButton new]; [self.view addSubview:self.backBtn]; self.backBtn.frame = CGRectMake(0, 0, KScreenWidth, KScreenHeight); [self.backBtn addTarget:self action:@selector(backBtnAction) forControlEvents:UIControlEventTouchUpInside]; } - (void)installUI { self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4]; [self setupWebViewConfiguration]; [self setupWebViewConfiguration]; [self loadGameContent]; } - (void)setupWebViewConfiguration { MSWeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[MSWeakWebViewScriptMessageDelegate alloc] initWithDelegate:self]; _ms_userContentController = [[WKUserContentController alloc] init]; [_ms_userContentController addScriptMessageHandler:weakScriptMessageDelegate name:kMSGetConfig]; [_ms_userContentController addScriptMessageHandler:weakScriptMessageDelegate name:kMSDestroy]; [_ms_userContentController addScriptMessageHandler:weakScriptMessageDelegate name:kMSGameRecharge]; [_ms_userContentController addScriptMessageHandler:weakScriptMessageDelegate name:kMSGameLoaded]; /// LEADERCC [_ms_userContentController addScriptMessageHandler:weakScriptMessageDelegate name:@"closeGame"]; [_ms_userContentController addScriptMessageHandler:weakScriptMessageDelegate name:@"pay"]; /// JOYPLAY [_ms_userContentController addScriptMessageHandler:weakScriptMessageDelegate name:kJPClose]; [_ms_userContentController addScriptMessageHandler:weakScriptMessageDelegate name:kJPRecharge]; [_ms_userContentController addScriptMessageHandler:weakScriptMessageDelegate name:kJPClickRecharge]; WKWebViewConfiguration *config = [WKWebViewConfiguration new]; config.allowsInlineMediaPlayback = YES; config.mediaTypesRequiringUserActionForPlayback = NO; [config setValue:@YES forKey:@"allowUniversalAccessFromFileURLs"]; //⾳视频的播放不需要⽤⼾⼿势触发, 即为⾃动播放 config.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone; config.preferences = [[WKPreferences alloc]init]; WKPreferences *preferences = [WKPreferences new]; preferences.javaScriptCanOpenWindowsAutomatically = YES; config.preferences = preferences; config.preferences.javaScriptEnabled = YES; config.userContentController = _ms_userContentController; CGRect frame = CGRectZero; if (self.gameType == MSGameTypeBaiShun) { frame = CGRectMake(0,0, KScreenWidth, KScreenHeight); } else { if(self.gameModel.showType == ActivityShowType_Half){ frame = CGRectMake(0, KScreenHeight * 0.3, KScreenWidth, KScreenHeight * 0.7); } else { frame = CGRectMake(0,0, KScreenWidth, KScreenHeight); } } self.webView = [[WKWebView alloc] initWithFrame:frame configuration:config]; [self.webView.scrollView setBackgroundColor:[UIColor clearColor]]; [self.webView setBackgroundColor:[UIColor clearColor]]; [self.webView setUIDelegate:self]; self.webView.navigationDelegate = self; //设置⽹⻚透明 [self.webView setOpaque:NO]; //设置⽹⻚全屏 if (@available(iOS 11.0, *)) { self.webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } [self.view addSubview:self.webView]; } - (void)loadGameContent { NSString *h5Url = self.gameModel.skipContent; NSURL *url = [self handleGameURL:h5Url]; if (!url) { [self hideHUD]; [XNDJTDDLoadingTool showErrorWithMessage:@"游戏链接无效"]; return; } NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0]; self.lastRequest = request; [self.webView loadRequest:request]; } - (BOOL)isValidGameURL:(NSString *)urlString { if ([NSString isEmpty:urlString]) { return NO; } NSURL *url = [NSURL URLWithString:urlString]; return (url && url.scheme && url.host); } - (NSURL *)handleGameURL:(NSString *)url { if (![self isValidGameURL:url]) { return nil; } switch (self.gameType) { case MSGameTypeBaiShun: url = [self addSupportLanguage:url]; return [NSURL URLWithString:url]; case MSGameTypeJoyPlay: url = [url stringByAppendingFormat:@"?gameId=%@", @(self.gameModel.gameModel.gameId)]; url = [url stringByAppendingFormat:@"&appKey=%@", self.gameModel.gameModel.appKey]; url = [url stringByAppendingFormat:@"&mini=%@", @(self.gameModel.showType == ActivityShowType_Half ? 1 : 0)]; if (self.gameModel.showType == ActivityShowType_Full) { url = [url stringByAppendingFormat:@"&safeTop=1"]; } url = [url stringByAppendingFormat:@"&roomId=%ld", (long)self.hostDelegate.getRoomInfo.uid]; break; case MSGameTypeCC: url = [url stringByAppendingFormat:@"&roomid=%ld", (long)self.hostDelegate.getRoomInfo.uid]; break; default: break; } url = [self addSupportLanguage:url]; if (![NSString isEmpty:self.gameModel.gameModel.urlParam]) { url = [url stringByAppendingString:[NSString stringWithFormat:@"&%@", self.gameModel.gameModel.urlParam]]; } url = [url stringByAppendingFormat:@"&token=%@", self.gameModel.gameModel.code]; url = [url stringByAppendingFormat:@"&uid=%@", [AccountInfoStorage instance].getUid]; return [NSURL URLWithString:url]; } - (NSString *)addSupportLanguage:(NSString *)url { NSString *mark = @"&"; NSString *lang = @"en-US"; if (![url containsString:@"?"]) { mark = @"?"; } if (isMSTR()) { lang = @"tr-TR"; } else if (isMSZH()) { lang = @"zh-TW"; } else if (isMSRTL()) { lang = @"ar-EG"; } else if (isMSPT()) { lang = @"pt-BR"; } else if (isMSES()) { lang = @"es-ES"; } url = [url stringByAppendingFormat:@"%@lang=%@", mark, lang]; return url; } - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { [self hideHUD]; self.retryCount = 0; [self.loadingTimer invalidate]; self.loadingTimer = nil; if (self.gameType != MSGameTypeBaiShun) { self.backBtn.frame = CGRectMake(0, 0, KScreenWidth, KScreenHeight*0.3); } // 注入错误处理脚本 NSString *errorHandler = @"window.onerror = function(msg, url, line, col, error) { window.webkit.messageHandlers.error.postMessage({message: msg, url: url, line: line, col: col}); return false; }"; [webView evaluateJavaScript:errorHandler completionHandler:nil]; #if DEBUG NSString *fileName = @"vconsole.min.js"; // 你要查找的文件名 NSString *directory = [[NSBundle mainBundle] resourcePath]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:directory]; NSString *filePath; for (NSString *path in enumerator) { if ([[path lastPathComponent] containsString:fileName]) { filePath = [directory stringByAppendingPathComponent:path]; break; } } if (filePath.length > 0) { NSString *vConsoleScript = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; [self.webView evaluateJavaScript:vConsoleScript completionHandler:nil]; // 初始化 vConsole NSString *initVConsoleScript = @"var vConsole = new VConsole();"; [self.webView evaluateJavaScript:initVConsoleScript completionHandler:nil]; } #endif } - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { [self handleWebViewError:error]; } - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { [self handleWebViewError:error]; } - (void)handleWebViewError:(NSError *)error { if (self.retryCount < 3) { self.retryCount++; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.webView loadRequest:self.lastRequest]; }); } else { [self hideHUD]; [XNDJTDDLoadingTool showErrorWithMessage:@"网络连接失败,请稍后重试"]; } } - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSURLCredential *card = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential, card); } else { completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil); } } // 调⽤JS - (void)callJs:(NSString*)method withJavaScriptValue:(nullable id)arguments { @kWeakify(self); if (arguments) { NSData *data = [NSJSONSerialization dataWithJSONObject:arguments options:(NSJSONWritingPrettyPrinted) error:nil]; NSString *jsonStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSString *jsMethods = [NSString stringWithFormat:@"%@(%@)", method, jsonStr]; [self.webView evaluateJavaScript:jsMethods completionHandler:^(id _Nullable resp, NSError * _Nullable error) { @kStrongify(self); NSLog(@"error = %@ , response = %@",error, resp); [self.backBtn removeFromSuperview]; }]; } else { NSString *jsMethods = [NSString stringWithFormat:@"%@({})", method]; [self.webView evaluateJavaScript:jsMethods completionHandler:^(id _Nullable resp, NSError * _Nullable error) { @kStrongify(self); NSLog(@"error = %@ , response = %@",error, resp); [self.backBtn removeFromSuperview]; }]; } } - (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString { if (jsonString == nil && jsonString.length == 0) { return nil; } NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; NSError *err; NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err]; if(err) { NSLog(@"json解析失败:%@",err); return nil; } return dic; } // 绑定协议⽅法 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { /// BaiShun switch (self.gameType) { case MSGameTypeBaiShun: { NSDictionary *dicBody = [self dictionaryWithJsonString:message.body]; if ([message.name isEqualToString:kMSGetConfig]) { [self getConfig:dicBody]; } else if ([message.name isEqualToString:kMSDestroy]) { [self destroy:dicBody]; } else if ([message.name isEqualToString:kMSGameLoaded]) { [self gameLoaded:dicBody]; } else if ([message.name isEqualToString:kMSGameRecharge]) { [self gameRecharge:dicBody]; } else { NSLog(@"未实现⽅法 : %@ --> %@", message.name, message.body); } break; } case MSGameTypeJoyPlay: { if ([message.name isEqualToString:kJPRecharge]) { XPIAPRechargeViewController * webVC =[[XPIAPRechargeViewController alloc] init]; webVC.type = @"4"; [self.navigationController pushViewController:webVC animated:YES]; self.isCharing = YES; } else if ([message.name isEqualToString:kJPClickRecharge]) { [self gameRecharge:@{}]; } else if ([message.name isEqualToString:kJPClose]) { [self backBtnAction]; } break; } case MSGameTypeCC: { /// LeaderCC NSString* method = [NSString stringWithFormat:@"%@:", message.name]; SEL selector = NSSelectorFromString(method); if([self respondsToSelector:selector]){ [self performSelector:selector withObject:message.body]; }else{ NSLog(@"未實現方法 : %@ --> %@", message.name, message.body); } break; } } } #pragma mark - LeaderCC Delegate // 关闭游戏 - (void)closeGame:(NSDictionary*)args { [self backBtnAction]; } // 显示充值界面 - (void)pay:(NSDictionary*)args { //拉起充值商城 TTAlertConfig *config = [[TTAlertConfig alloc]init]; config.title = YMLocalizedString(@"UserDetail_CP_Toast_0"); config.message = YMLocalizedString(@"XPNobleCenterViewController3"); config.actionStyle = TTAlertActionBothStyle; @kWeakify(self); [TTPopup alertWithConfig:config showBorder:NO confirmHandler:^{ @kStrongify(self); XPIAPRechargeViewController * webVC =[[XPIAPRechargeViewController alloc] init]; webVC.type = @"4"; [self.navigationController pushViewController:webVC animated:YES]; self.isCharing = YES; } cancelHandler:^{ }]; } - (void)updateCoin { // 平时游戏币更新不需要调用该方法,以防止影响游戏开奖动画。 [self.webView evaluateJavaScript:@"updateCoin()" completionHandler:^(id _Nullable result, NSError * _Nullable error) { if (error) { NSLog(@"Error calling JS function: %@", error.localizedDescription); } else { NSLog(@"JS function called successfully, result: %@", result); } }]; } #pragma mark - BaiShun Delegate // 获取信息配置 - (void) getConfig:(NSDictionary*)args { NSLog(@"BSGAME %s","游戏调⽤getConfig"); NSString* method = [args objectForKey:@"jsCallback"]; RoomInfoModel *roomInfo = self.hostDelegate.getRoomInfo; ActivityInfoItemModel *gameModel = self.gameModel.gameModel; NSString *appChannel = gameModel.appChannel ?: @""; int64_t appId = gameModel.appId; NSString *userId = [AccountInfoStorage instance].getUid; NSString *code = gameModel.code ?: @""; NSString *roomId = [NSString stringWithFormat:@"%ld",roomInfo.uid]; NSString *gameMode = gameModel.gameMode ?: @""; NSDictionary *gameConfig = gameModel.gameConfig ?: @{}; int gsp = gameModel.gsp; // 数据只是参考值,需要根据⾃⼰APP进⾏赋值 NSObject* configData = @{ @"appChannel":appChannel, @"appId":@(appId), @"userId":userId, @"code":code, @"roomId":roomId, @"gameMode":gameMode, @"language":[self getlanguage], @"gameConfig":gameConfig, @"gsp":@(gsp), }; [self callJs:method withJavaScriptValue:configData]; } -(NSString *)getlanguage{ NSString *language = [NSBundle getLanguageText]; if ([language hasPrefix:@"zh"]) { if ([language rangeOfString:@"Hans"].location != NSNotFound) { language = @"0"; // 简体中文 } else { language = @"1"; // 繁體中文 } }else if([language hasPrefix:@"ar"]){///阿拉伯语 language = @"7"; }else{///英文 language = @"2"; } return language; } // 销毁游戏 - (void) destroy:(NSDictionary*)args { NSLog(@"BSGAME %s","游戏调⽤destroy"); //关闭游戏 [self willMoveToParentViewController:nil]; //1 [self.view removeFromSuperview]; //2 [self removeFromParentViewController]; //3 } -(void)backBtnAction{ TTAlertConfig *config = [[TTAlertConfig alloc]init]; config.title = YMLocalizedString(@"UserDetail_CP_Toast_0"); config.message = YMLocalizedString(@"MSRoomGameWebVC0"); config.actionStyle = TTAlertActionBothStyle; @kWeakify(self); [TTPopup alertWithConfig:config showBorder:NO confirmHandler:^{ @kStrongify(self); [self destroy:nil]; } cancelHandler:^{ }]; } // 提⽰余额不⾜ - (void) gameRecharge:(NSDictionary*)args { NSLog(@"BSGAME %s","游戏调⽤gameRecharge"); //拉起充值商城 TTAlertConfig *config = [[TTAlertConfig alloc]init]; config.title = YMLocalizedString(@"UserDetail_CP_Toast_0"); config.message = YMLocalizedString(@"XPNobleCenterViewController3"); config.actionStyle = TTAlertActionBothStyle; @kWeakify(self); [TTPopup alertWithConfig:config showBorder:NO confirmHandler:^{ @kStrongify(self); XPIAPRechargeViewController * webVC =[[XPIAPRechargeViewController alloc] init]; webVC.type = @"4"; [self.navigationController pushViewController:webVC animated:YES]; self.isCharing = YES; } cancelHandler:^{ }]; } // 游戏加载完毕 - (void) gameLoaded:(NSDictionary*)args { // 游戏加载完毕 [self.backBtn removeFromSuperview]; } @end