feat(files): веб-сессия Nextcloud для .whiteboard в WebView
Доска (whiteboard) открывается через веб-viewer, которому мало Basic-auth: Vue-XHR ходят по session-cookie+CSRF. Теперь перед загрузкой web-файла логинимся формой /login app-паролем (NextcloudWebSession, обходит 2FA), переносим cookie в CookieManager и грузим страницу нативно — для web-файлов отключено OkHttp-проксирование (оно теряло тело POST/PROPFIND → «нет файлов»). Collabora-путь не тронут (WOPI-токен самодостаточен). +NetworkFactory.newBaseBuilder — билдер на общем пуле без auth.
This commit is contained in:
@@ -40,12 +40,17 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import ru.forbion.f7cloud.core.designsystem.F7Colors
|
||||
import ru.forbion.f7cloud.core.designsystem.F7PrimaryButton
|
||||
import ru.forbion.f7cloud.core.designsystem.F7Theme
|
||||
import ru.forbion.f7cloud.core.network.NetworkFactory
|
||||
import ru.forbion.f7cloud.core.network.NextcloudWebSession
|
||||
import ru.forbion.f7cloud.feature.files.OfficeEditorLaunch
|
||||
|
||||
class OfficeEditorActivity : ComponentActivity() {
|
||||
@@ -200,7 +205,55 @@ class OfficeEditorActivity : ComponentActivity() {
|
||||
if (current == next) return
|
||||
editorVisible = false
|
||||
loadError = null
|
||||
view.loadUrl(target.url)
|
||||
startLoad(view, target)
|
||||
}
|
||||
|
||||
/**
|
||||
* Collabora — грузим сразу (iframe самодостаточен по WOPI-токену). Web-viewer (доска и пр.)
|
||||
* требует НАСТОЯЩУЮ веб-сессию: сначала логинимся app-паролем, переносим cookie в
|
||||
* WebView CookieManager, только потом открываем URL — иначе viewer видит анонима («нет файлов»).
|
||||
*/
|
||||
private fun startLoad(view: WebView, target: OfficeEditorLaunch) {
|
||||
if (target.collaboraBaseUrl.isNotBlank()) {
|
||||
view.loadUrl(target.url)
|
||||
return
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val ok = runCatching { establishWebSession(target) }.getOrDefault(false)
|
||||
if (!ok) {
|
||||
loadError = "Не удалось открыть сессию. Проверьте интернет и попробуйте снова."
|
||||
return@launch
|
||||
}
|
||||
view.loadUrl(target.url)
|
||||
}
|
||||
}
|
||||
|
||||
/** Логин по форме `/login`, перенос session-cookie в WebView. Возвращает true при успехе. */
|
||||
private suspend fun establishWebSession(target: OfficeEditorLaunch): Boolean {
|
||||
val serverUrl = target.serverUrl.ifBlank { target.url }
|
||||
val cookies = withContext(Dispatchers.IO) {
|
||||
NextcloudWebSession.login(
|
||||
serverUrl = serverUrl,
|
||||
username = target.username,
|
||||
appPassword = target.password,
|
||||
trustAllCerts = target.trustAllCerts,
|
||||
)
|
||||
}
|
||||
if (cookies.isEmpty()) return false
|
||||
val cm = CookieManager.getInstance()
|
||||
cm.setAcceptCookie(true)
|
||||
webViewRef?.let { cm.setAcceptThirdPartyCookies(it, true) }
|
||||
val base = serverUrl.trimEnd('/')
|
||||
cookies.forEach { c ->
|
||||
val attrs = buildString {
|
||||
append("${c.name}=${c.value}; Path=${c.path}")
|
||||
if (c.secure) append("; Secure")
|
||||
if (c.httpOnly) append("; HttpOnly")
|
||||
}
|
||||
cm.setCookie(base, attrs)
|
||||
}
|
||||
withContext(Dispatchers.IO) { cm.flush() }
|
||||
return true
|
||||
}
|
||||
|
||||
private fun readLaunch(): OfficeEditorLaunch? {
|
||||
@@ -250,7 +303,7 @@ class OfficeEditorActivity : ComponentActivity() {
|
||||
val current = webView.url?.trimEnd('/').orEmpty()
|
||||
val target = launch.url.trimEnd('/')
|
||||
if (current != target) {
|
||||
webView.loadUrl(launch.url)
|
||||
startLoad(webView, launch)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -128,11 +128,10 @@ internal class OfficeWebViewClient(
|
||||
}
|
||||
|
||||
fun buildInterceptPrefixes(launch: OfficeEditorLaunch): List<String> {
|
||||
val prefixes = mutableListOf(launch.serverUrl.trimEnd('/'))
|
||||
if (launch.collaboraBaseUrl.isNotBlank()) {
|
||||
prefixes += launch.collaboraBaseUrl.trimEnd('/')
|
||||
}
|
||||
return prefixes.distinct()
|
||||
// Web-файлы (whiteboard): НЕ проксируем — WebView ходит нативно по веб-сессии
|
||||
// (cookie+CSRF). Проксирование через OkHttp теряло тело POST/PROPFIND → пустой Files.
|
||||
if (launch.collaboraBaseUrl.isBlank()) return emptyList()
|
||||
return listOf(launch.serverUrl.trimEnd('/'), launch.collaboraBaseUrl.trimEnd('/')).distinct()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user