1+ package com.jankinwu.fntv.client.processor
2+
3+ import co.touchlab.kermit.Logger
4+ import com.jankinwu.fntv.client.data.model.LoginHistory
5+ import com.jankinwu.fntv.client.data.model.request.AuthRequest
6+ import com.jankinwu.fntv.client.data.network.impl.FnOfficialApiImpl
7+ import com.jankinwu.fntv.client.data.store.AccountDataCache
8+ import com.jankinwu.fntv.client.manager.LoginStateManager
9+ import com.jankinwu.fntv.client.manager.PreferencesManager
10+ import com.jankinwu.fntv.client.ui.component.common.ToastManager
11+ import com.jankinwu.fntv.client.ui.component.common.ToastType
12+ import com.multiplatform.webview.cookie.Cookie
13+ import com.multiplatform.webview.web.WebViewNavigator
14+ import com.multiplatform.webview.web.WebViewState
15+ import kotlinx.serialization.json.Json
16+ import kotlinx.serialization.json.JsonObject
17+ import kotlinx.serialization.json.contentOrNull
18+ import kotlinx.serialization.json.jsonObject
19+ import kotlinx.serialization.json.jsonPrimitive
20+
21+ class NetworkMessageProcessor (
22+ private val fnOfficialApi : FnOfficialApiImpl ,
23+ private val toastManager : ToastManager ,
24+ private val webViewState : WebViewState ,
25+ private val navigator : WebViewNavigator ,
26+ private val onLoginSuccess : (LoginHistory ) -> Unit ,
27+ private val fnId : String ,
28+ private val autoLoginUsername : String?
29+ ) {
30+ private val logger = Logger .withTag(" NetworkMessageProcessor" )
31+ private var isAuthRequested = false
32+ private var isSysConfigInFlight = false
33+ private var isSysConfigLoaded = false
34+
35+ suspend fun process (
36+ params : String ,
37+ baseUrl : String ,
38+ onBaseUrlChange : (String ) -> Unit ,
39+ capturedUsername : String ,
40+ capturedPassword : String ,
41+ capturedRememberPassword : Boolean
42+ ) {
43+ logger.i(" Intercepted: $params " )
44+ try {
45+ val json = Json .parseToJsonElement(params).jsonObject
46+ val type = json[" type" ]?.jsonPrimitive?.contentOrNull
47+ val url = json[" url" ]?.jsonPrimitive?.contentOrNull ? : " "
48+
49+ if (type == " XHR" && url.contains(" /sac/rpcproxy/v1/new-user-guide/status" )) {
50+ handleXhrMessage(json, baseUrl, onBaseUrlChange)
51+ } else if (type == " Response" && url.contains(" /oauthapi/authorize" )) {
52+ handleResponseMessage(json, baseUrl, capturedUsername, capturedPassword, capturedRememberPassword)
53+ }
54+ } catch (e: Exception ) {
55+ logger.e(" Handler error" , e)
56+ }
57+ }
58+
59+ private suspend fun handleXhrMessage (
60+ json : JsonObject ,
61+ baseUrl : String ,
62+ onBaseUrlChange : (String ) -> Unit
63+ ) {
64+ val cookie = json[" cookie" ]?.jsonPrimitive?.contentOrNull
65+ logger.i(" fnos cookie: $cookie " )
66+ if (! cookie.isNullOrBlank()) {
67+ AccountDataCache .mergeCookieString(cookie)
68+ if (baseUrl.contains(" 5ddd.com" )) {
69+ // 使用 FN Connect 外网访问必加此 Cookie 不然访问不了
70+ AccountDataCache .insertCookie(" mode" to " relay" )
71+ }
72+ if (! isSysConfigLoaded && ! isSysConfigInFlight) {
73+ isSysConfigInFlight = true
74+ try {
75+ val config = fnOfficialApi.getSysConfig()
76+ logger.i(" Got sys config: $config " )
77+ val oauth = config.nasOauth
78+ var currentBaseUrl = baseUrl
79+ if (oauth.url.isNotBlank() && oauth.url != " ://" ) {
80+ currentBaseUrl = oauth.url
81+ onBaseUrlChange(currentBaseUrl)
82+ }
83+ val appId = oauth.appId
84+ val redirectUri = " $currentBaseUrl /v/oauth/result"
85+ val targetUrl = " $currentBaseUrl /signin?client_id=$appId &redirect_uri=$redirectUri "
86+
87+ logger.i(" Navigating to OAuth: $targetUrl " )
88+ val domain = currentBaseUrl.substringAfter(" ://" ).substringBefore(" :" ).substringBefore(" /" )
89+ cookie.split(" ;" ).forEach {
90+ val parts = it.trim().split(" =" , limit = 2 )
91+ if (parts.size == 2 ) {
92+ val cookieObj = Cookie (
93+ name = parts[0 ],
94+ value = parts[1 ],
95+ domain = domain
96+ )
97+ webViewState.cookieManager.setCookie(currentBaseUrl, cookieObj)
98+ }
99+ }
100+ isSysConfigLoaded = true
101+ navigator.loadUrl(targetUrl)
102+ } catch (e: Exception ) {
103+ isSysConfigInFlight = false
104+ logger.e(" Failed to get sys config" , e)
105+ toastManager.showToast(" 获取系统配置失败: ${e.message} " , ToastType .Failed )
106+ }
107+ }
108+ }
109+ }
110+
111+ private suspend fun handleResponseMessage (
112+ json : JsonObject ,
113+ baseUrl : String ,
114+ capturedUsername : String ,
115+ capturedPassword : String ,
116+ capturedRememberPassword : Boolean
117+ ) {
118+ if (! isAuthRequested) {
119+ val body = json[" body" ]?.jsonPrimitive?.contentOrNull
120+ if (! body.isNullOrBlank()) {
121+ try {
122+ val bodyJson = Json .parseToJsonElement(body).jsonObject
123+ val data = bodyJson[" data" ]?.jsonObject
124+ val code = data?.get(" code" )?.jsonPrimitive?.contentOrNull
125+ if (code != null ) {
126+ isAuthRequested = true
127+ try {
128+ val response = fnOfficialApi.auth(AuthRequest (" Trim-NAS" , code))
129+ val token = response.token
130+ if (token.isNotBlank()) {
131+ AccountDataCache .authorization = token
132+ AccountDataCache .insertCookie(" Trim-MC-token" to token)
133+ logger.i(" cookie: ${AccountDataCache .cookieState} " )
134+ LoginStateManager .updateLoginStatus(true )
135+ toastManager.showToast(" 登录成功" , ToastType .Success )
136+
137+ val normalizedUsername = capturedUsername.trim()
138+ .ifBlank { autoLoginUsername?.trim().orEmpty() }
139+ if (normalizedUsername.isNotBlank()) {
140+ PreferencesManager .getInstance().addLoginUsernameHistory(normalizedUsername)
141+ }
142+ val shouldRemember = capturedRememberPassword && capturedPassword.isNotBlank()
143+ logger.i(" Remember password: $capturedRememberPassword " )
144+ val history = LoginHistory (
145+ host = " " ,
146+ port = 0 ,
147+ username = normalizedUsername,
148+ password = if (shouldRemember) capturedPassword else null ,
149+ isHttps = baseUrl.startsWith(" https" ),
150+ rememberPassword = shouldRemember,
151+ isNasLogin = true ,
152+ fnConnectUrl = baseUrl,
153+ fnId = fnId.trim()
154+ )
155+ onLoginSuccess(history)
156+ } else {
157+ isAuthRequested = false
158+ toastManager.showToast(" 登录失败: Token 为空" , ToastType .Failed )
159+ }
160+ } catch (e: Exception ) {
161+ isAuthRequested = false
162+ logger.e(" OAuth result failed" , e)
163+ toastManager.showToast(" 登录失败: ${e.message} " , ToastType .Failed )
164+ }
165+ }
166+ } catch (e: Exception ) {
167+ logger.e(" Failed to parse OAuth response" , e)
168+ }
169+ }
170+ }
171+ }
172+ }
0 commit comments