| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- //
- // LNWebViewController.swift
- // Lanu
- //
- // Created by OneeChan on 2025/12/12.
- //
- import Foundation
- import UIKit
- import SnapKit
- import WebKit
- import Combine
- import AutoCodable
- class LNJumpWebViewConfig {
- var url: String
- var customTitle: String = ""
- var showNavigationBar = false
- var setSafeArea = false
-
- static func normalConfig(url: String) -> LNJumpWebViewConfig {
- let config = LNJumpWebViewConfig(url: url)
- config.showNavigationBar = true
- config.setSafeArea = true
- return config
- }
-
- init(url: String, title: String = "") {
- self.url = url
- customTitle = title
- }
-
- init(param: LNWebPageDeeplinkParams) {
- url = param.url
- showNavigationBar = param.header
- setSafeArea = param.safeArea
- }
- }
- extension UIView {
- func pushToWebView(_ config: LNJumpWebViewConfig) {
- if config.url.isDeeplink, let url = URL(string: config.url) {
- LNDeeplinkManager.shared.handleDeepLink(url)
- return
- }
- let vc = LNWebViewController(config: config)
- navigationController?.pushViewController(vc, animated: true)
- }
-
- func pushToWebView(_ param: LNWebPageDeeplinkParams) {
- let config = LNJumpWebViewConfig(param: param)
- let vc = LNWebViewController(config: config)
- navigationController?.pushViewController(vc, animated: true)
- }
- }
- class LNWebViewController: LNViewController {
- private let config: LNJumpWebViewConfig
- private var webView: WKWebView?
-
- init(config: LNJumpWebViewConfig) {
- self.config = config
- super.init(nibName: nil, bundle: nil)
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- setupViews()
-
- loadUrl()
- }
- }
- extension LNWebViewController {
- private func loadUrl() {
- guard let url = URL(string: config.url) else { return }
-
- webView?.load(URLRequest(url: url))
- }
- }
- extension LNWebViewController: WKNavigationDelegate {
- func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
- decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
- guard let url = navigationAction.request.url,
- let scheme = url.scheme else {
- decisionHandler(.allow) // 无 URL/Schema,允许默认行为
- return
- }
-
- guard scheme.lowercased() == LNDeeplinkUrls.appScheme else {
- decisionHandler(.allow)
- return
- }
- let fullPath = if let host = url.host {
- "\(host)\(url.path)"
- } else {
- url.path
- }
- if fullPath == LNDeeplinkUrls.web.closeWindow.deeplinkPath {
- navigationController?.popViewController(animated: true)
- } else if fullPath == LNDeeplinkUrls.web.userCancellation.deeplinkPath {
- LNAccountManager.shared.clean()
- } else {
- LNDeeplinkManager.shared.handleDeepLink(url)
- }
-
- decisionHandler(.cancel)
- }
- }
- extension LNWebViewController {
- private func setupViews() {
- if !config.customTitle.isEmpty {
- title = config.customTitle
- }
- showNavigationBar = config.showNavigationBar
-
- let webView = buildWebView()
- view.addSubview(webView)
- webView.snp.makeConstraints { make in
- make.horizontalEdges.equalToSuperview()
- if config.showNavigationBar || !config.setSafeArea {
- make.top.equalToSuperview()
- } else if config.setSafeArea {
- make.top.equalToSuperview().offset(UIView.statusBarHeight)
- }
- if config.setSafeArea {
- make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
- } else {
- make.bottom.equalToSuperview()
- }
- }
- }
-
- private func buildWebView() -> UIView {
- let webConfig = WKWebViewConfiguration()
- setupDefaultLocalStorage(webConfig)
-
- let webView = WKWebView(frame: .zero, configuration: webConfig)
- webView.navigationDelegate = self
- #if DEBUG
- if #available(iOS 16.4, *) {
- webView.isInspectable = true
- }
- #endif
- webView.scrollView.contentInsetAdjustmentBehavior = .never
- webView.scrollView.bounces = false
-
- setupDefaultCookie(webView)
-
- webView.publisher(for: \.title).removeDuplicates().sink
- { [weak self] webTitle in
- guard let self else { return }
- guard config.customTitle.isEmpty else { return }
-
- title = webTitle
- }.store(in: &cancellables)
-
- self.webView = webView
-
- return webView
- }
-
- private func setupDefaultLocalStorage(_ webConfig: WKWebViewConfiguration) {
- let tokenScript = WKUserScript(
- source: "window.localStorage.setItem('GAMI-web_auth_token', '\(LNAccountManager.shared.token)')",
- injectionTime: .atDocumentStart, forMainFrameOnly: true)
- webConfig.userContentController.addUserScript(tokenScript)
-
- let languageScript = WKUserScript(
- source: "window.localStorage.setItem('GAMI-web_app_locale', '\(LNAppConfig.shared.curLang.bundleName)')",
- injectionTime: .atDocumentStart, forMainFrameOnly: true)
- webConfig.userContentController.addUserScript(languageScript)
-
- var header: [String: Any] = [:]
- header["time"] = curTimeInMicro
- header["network"] = LNNetworkMonitor.curNetworkType.desc
- header["version"] = curBuildVersion
- header["api"] = 1
- header["channel"] = "appStore"
- header["platform"] = "2"
- header["device"] = curDeviceModelName
- header["app"] = curAppBundleIdentifier
- header["udid"] = curDeviceId
- header["token"] = LNAccountManager.shared.token
- if let jsonData = try? JSONSerialization.data(withJSONObject: header),
- let jsonStr = String(data: jsonData, encoding: .utf8) {
- let headerScript = WKUserScript(
- source: "javascript:window.GAMI_BRIDGE={'requestHeader': \(jsonStr), 'safeArea': \"\(UIView.statusBarHeight)|\(view.safeBottomInset)\"}",
- injectionTime: .atDocumentStart,
- forMainFrameOnly: true)
- webConfig.userContentController.addUserScript(headerScript)
- }
- }
-
- private func setupDefaultCookie(_ webView: WKWebView) {
- // 创建 Cookie
- if let tokenCookie = HTTPCookie(properties: [
- .domain: ".gami.vip",
- .path: "/",
- .name: "GAMI-cookie_auth_token",
- .value: LNAccountManager.shared.token,
- .secure: "TRUE",
- .expires: NSDate(timeIntervalSinceNow: 3600)
- ]) {
- webView.configuration.websiteDataStore.httpCookieStore.setCookie(tokenCookie)
- }
- if let languageCookie = HTTPCookie(properties: [
- .domain: ".gami.vip",
- .path: "/",
- .name: "GAMI-cookie_app_locale",
- .value: LNAppConfig.shared.curLang.bundleName,
- .secure: "TRUE",
- .expires: NSDate(timeIntervalSinceNow: 3600)
- ]) {
- webView.configuration.websiteDataStore.httpCookieStore.setCookie(languageCookie)
- }
- if let languageCookie = HTTPCookie(properties: [
- .domain: ".gami.vip",
- .path: "/",
- .name: "GAMI-cookie_safe_area",
- .value: "\(UIView.statusBarHeight)|\(view.safeBottomInset)",
- .secure: "TRUE",
- .expires: NSDate(timeIntervalSinceNow: 3600)
- ]) {
- webView.configuration.websiteDataStore.httpCookieStore.setCookie(languageCookie)
- }
- }
- }
|