mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-11-04 10:13:59 +00:00
fix(ios): make the login WebView identify as Mobile Safari
accounts.spotify.com rejected iOS logins ("Oops, something went wrong") because
the WebView sent WKWebView's default User-Agent, which lacks Safari's
"Version/x ... Safari/604.1" and reads as an embedded browser. The random
User-Agent header never reached any request: the screen replaces the content
before it loads.
Have WebKit build Safari's exact UA (with its frozen OS version) via a probe
WKWebView using Safari's applicationNameForUserAgent, prime it at startup and
apply it through webSettings.customUserAgentString so every request uses it.
This commit is contained in:
parent
cc21568457
commit
bdf3655a23
@ -27,6 +27,7 @@ import androidx.compose.ui.window.ComposeUIViewController
|
|||||||
import com.yushosei.newpipe.extractor.NewPipe
|
import com.yushosei.newpipe.extractor.NewPipe
|
||||||
import com.yushosei.newpipe.util.DefaultDownloaderImpl
|
import com.yushosei.newpipe.util.DefaultDownloaderImpl
|
||||||
import org.spotube.spotube.core.di.initKoin
|
import org.spotube.spotube.core.di.initKoin
|
||||||
|
import org.spotube.spotube.core.webview.SafariUserAgent
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
@ -35,6 +36,7 @@ import org.koin.core.component.inject
|
|||||||
|
|
||||||
fun MainViewController() = ComposeUIViewController(configure = {
|
fun MainViewController() = ComposeUIViewController(configure = {
|
||||||
initKoin()
|
initKoin()
|
||||||
|
SafariUserAgent.prime()
|
||||||
}) { InitNewPipe { App() } }
|
}) { InitNewPipe { App() } }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@ -27,6 +27,8 @@ import platform.WebKit.WKWebsiteDataTypeCookies
|
|||||||
import platform.WebKit.WKWebsiteDataTypeLocalStorage
|
import platform.WebKit.WKWebsiteDataTypeLocalStorage
|
||||||
|
|
||||||
actual fun platformWebviewConfig(webView: WebViewState, pluginId: String?) {
|
actual fun platformWebviewConfig(webView: WebViewState, pluginId: String?) {
|
||||||
|
// Applied to the WKWebView itself, so it covers redirects and XHR too, not just the first request.
|
||||||
|
SafariUserAgent.value?.let { webView.webSettings.customUserAgentString = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalForeignApi::class)
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
|
|||||||
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.spotube.spotube.core.webview
|
||||||
|
|
||||||
|
import co.touchlab.kermit.Logger
|
||||||
|
import kotlinx.cinterop.ExperimentalForeignApi
|
||||||
|
import kotlinx.cinterop.readValue
|
||||||
|
import platform.CoreGraphics.CGRectZero
|
||||||
|
import platform.UIKit.UIDevice
|
||||||
|
import platform.UIKit.UIUserInterfaceIdiomPad
|
||||||
|
import platform.WebKit.WKWebView
|
||||||
|
import platform.WebKit.WKWebViewConfiguration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mobile Safari's exact User-Agent on this device.
|
||||||
|
*
|
||||||
|
* WKWebView's default UA ends in `Mobile/15E148` without Safari's `Version/x … Safari/604.1`,
|
||||||
|
* which sites like accounts.spotify.com treat as an embedded browser ("Oops, something went
|
||||||
|
* wrong"). Hand-building the string isn't safe either: Safari freezes the OS part (iOS 26
|
||||||
|
* reports `iPhone OS 18_7`). So WebKit builds it: a probe WKWebView gets Safari's application
|
||||||
|
* name and reports `navigator.userAgent`, which is cached for every web view.
|
||||||
|
*/
|
||||||
|
internal object SafariUserAgent {
|
||||||
|
var value: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
// Kept alive until its async evaluation calls back.
|
||||||
|
private var probe: WKWebView? = null
|
||||||
|
|
||||||
|
/** Must run on the main thread (WebKit); call once at startup. */
|
||||||
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
|
fun prime() {
|
||||||
|
if (value != null || probe != null) return
|
||||||
|
val config = WKWebViewConfiguration().apply { applicationNameForUserAgent = safariApplicationName() }
|
||||||
|
probe = WKWebView(frame = CGRectZero.readValue(), configuration = config).also { webView ->
|
||||||
|
webView.evaluateJavaScript("navigator.userAgent") { result, _ ->
|
||||||
|
value = result as? String
|
||||||
|
probe = null
|
||||||
|
Logger.withTag("SafariUserAgent").i { "WebView User-Agent: $value" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// What Safari appends after WebKit's base UA; iPad Safari sends the desktop-class form.
|
||||||
|
private fun safariApplicationName(): String {
|
||||||
|
val version = UIDevice.currentDevice.systemVersion.split('.').take(2).joinToString(".")
|
||||||
|
return if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||||
|
"Version/$version Safari/605.1.15"
|
||||||
|
} else {
|
||||||
|
"Version/$version Mobile/15E148 Safari/604.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user