Add proxy routing and sing-box support

This commit is contained in:
Fabian Freund
2026-05-22 18:16:31 +02:00
parent 51289f1266
commit a5974617aa
262 changed files with 32003 additions and 3962 deletions
@@ -10,6 +10,7 @@ import eu.weblibre.flutter_mozilla_components.feature.BrowserExtensionFeature
import eu.weblibre.flutter_mozilla_components.feature.ContainerProxyFeature
import eu.weblibre.flutter_mozilla_components.feature.ResultConsumer
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoContainerProxyApi
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoProxySettings
import org.json.JSONObject
class GeckoContainerProxyApiImpl : GeckoContainerProxyApi {
@@ -25,6 +26,38 @@ class GeckoContainerProxyApiImpl : GeckoContainerProxyApi {
ContainerProxyFeature.scheduleRequest("removeContainerProxy", contextId)
}
override fun upsertProxy(proxy: GeckoProxySettings) {
ContainerProxyFeature.scheduleRequest("upsertProxy", proxy.toJson())
}
override fun removeProxy(proxyId: String) {
ContainerProxyFeature.scheduleRequest("removeProxy", proxyId)
}
override fun setContainerProxy(contextId: String, proxyId: String) {
ContainerProxyFeature.scheduleRequest(
"setContainerProxy",
JSONObject().apply {
put("contextId", contextId)
put("proxyId", proxyId)
}
)
}
override fun clearContainerProxy(contextId: String) {
ContainerProxyFeature.scheduleRequest("clearContainerProxy", contextId)
}
override fun removeContainerProxyRelation(contextId: String, proxyId: String) {
ContainerProxyFeature.scheduleRequest(
"removeContainerProxyRelation",
JSONObject().apply {
put("contextId", contextId)
put("proxyId", proxyId)
}
)
}
override fun setSiteAssignments(assignments: Map<String, String>) {
ContainerProxyFeature.scheduleRequest("setSiteAssignments", JSONObject(assignments))
}
@@ -42,4 +75,18 @@ class GeckoContainerProxyApiImpl : GeckoContainerProxyApi {
}
})
}
}
private fun GeckoProxySettings.toJson(): JSONObject {
return JSONObject().apply {
put("id", id)
put("title", title)
put("type", type)
put("host", host)
put("port", port)
username?.let { put("username", it) }
password?.let { put("password", it) }
put("proxyDNS", proxyDNS)
put("doNotProxyLocal", doNotProxyLocal)
}
}
}
@@ -12,9 +12,11 @@ import android.content.Intent
import android.net.Uri
import android.util.Log
import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
import eu.weblibre.flutter_mozilla_components.feature.InertExternalSchemes
import eu.weblibre.flutter_mozilla_components.feature.SandboxCaptureBridge
import eu.weblibre.flutter_mozilla_components.feature.SandboxCaptureRegistry
import eu.weblibre.flutter_mozilla_components.pigeons.ProxyLoadError
import mozilla.components.browser.errorpages.ErrorPages
import mozilla.components.browser.errorpages.ErrorType
import mozilla.components.browser.state.selector.findTabOrCustomTab
@@ -145,6 +147,21 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
errorType: ErrorType,
uri: String?,
): RequestInterceptor.ErrorResponse {
if (errorType == ErrorType.ERROR_PROXY_CONNECTION_REFUSED ||
errorType == ErrorType.ERROR_UNKNOWN_PROXY_HOST
) {
val tab = components.core.store.state.findTabOrCustomTab(session)
components.flutterEvents.onProxyLoadError(
EventSequence.next(),
ProxyLoadError(
tabId = tab?.id,
contextId = tab?.contextId,
url = uri,
errorType = errorType.name,
)
) { _ -> }
}
val errorPage = ErrorPages.createUrlEncodedErrorPage(context, errorType, uri)
return RequestInterceptor.ErrorResponse(errorPage)
}
@@ -4573,6 +4573,72 @@ data class MlProgressData (
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class GeckoProxySettings (
val id: String,
val title: String,
val type: String,
val host: String,
val port: Long,
val username: String? = null,
val password: String? = null,
val proxyDNS: Boolean,
val doNotProxyLocal: Boolean
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): GeckoProxySettings {
val id = pigeonVar_list[0] as String
val title = pigeonVar_list[1] as String
val type = pigeonVar_list[2] as String
val host = pigeonVar_list[3] as String
val port = pigeonVar_list[4] as Long
val username = pigeonVar_list[5] as String?
val password = pigeonVar_list[6] as String?
val proxyDNS = pigeonVar_list[7] as Boolean
val doNotProxyLocal = pigeonVar_list[8] as Boolean
return GeckoProxySettings(id, title, type, host, port, username, password, proxyDNS, doNotProxyLocal)
}
}
fun toList(): List<Any?> {
return listOf(
id,
title,
type,
host,
port,
username,
password,
proxyDNS,
doNotProxyLocal,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as GeckoProxySettings
return GeckoPigeonUtils.deepEquals(this.id, other.id) && GeckoPigeonUtils.deepEquals(this.title, other.title) && GeckoPigeonUtils.deepEquals(this.type, other.type) && GeckoPigeonUtils.deepEquals(this.host, other.host) && GeckoPigeonUtils.deepEquals(this.port, other.port) && GeckoPigeonUtils.deepEquals(this.username, other.username) && GeckoPigeonUtils.deepEquals(this.password, other.password) && GeckoPigeonUtils.deepEquals(this.proxyDNS, other.proxyDNS) && GeckoPigeonUtils.deepEquals(this.doNotProxyLocal, other.doNotProxyLocal)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + GeckoPigeonUtils.deepHash(this.id)
result = 31 * result + GeckoPigeonUtils.deepHash(this.title)
result = 31 * result + GeckoPigeonUtils.deepHash(this.type)
result = 31 * result + GeckoPigeonUtils.deepHash(this.host)
result = 31 * result + GeckoPigeonUtils.deepHash(this.port)
result = 31 * result + GeckoPigeonUtils.deepHash(this.username)
result = 31 * result + GeckoPigeonUtils.deepHash(this.password)
result = 31 * result + GeckoPigeonUtils.deepHash(this.proxyDNS)
result = 31 * result + GeckoPigeonUtils.deepHash(this.doNotProxyLocal)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class ContainerSiteAssignment (
val requestId: String,
@@ -4623,6 +4689,52 @@ data class ContainerSiteAssignment (
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class ProxyLoadError (
val tabId: String? = null,
val contextId: String? = null,
val url: String? = null,
val errorType: String
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): ProxyLoadError {
val tabId = pigeonVar_list[0] as String?
val contextId = pigeonVar_list[1] as String?
val url = pigeonVar_list[2] as String?
val errorType = pigeonVar_list[3] as String
return ProxyLoadError(tabId, contextId, url, errorType)
}
}
fun toList(): List<Any?> {
return listOf(
tabId,
contextId,
url,
errorType,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as ProxyLoadError
return GeckoPigeonUtils.deepEquals(this.tabId, other.tabId) && GeckoPigeonUtils.deepEquals(this.contextId, other.contextId) && GeckoPigeonUtils.deepEquals(this.url, other.url) && GeckoPigeonUtils.deepEquals(this.errorType, other.errorType)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + GeckoPigeonUtils.deepHash(this.tabId)
result = 31 * result + GeckoPigeonUtils.deepHash(this.contextId)
result = 31 * result + GeckoPigeonUtils.deepHash(this.url)
result = 31 * result + GeckoPigeonUtils.deepHash(this.errorType)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class GeckoHeader (
val key: String,
@@ -5995,75 +6107,85 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
}
235.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ContainerSiteAssignment.fromList(it)
GeckoProxySettings.fromList(it)
}
}
236.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
GeckoHeader.fromList(it)
ContainerSiteAssignment.fromList(it)
}
}
237.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
GeckoFetchRequest.fromList(it)
ProxyLoadError.fromList(it)
}
}
238.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
GeckoFetchResponse.fromList(it)
GeckoHeader.fromList(it)
}
}
239.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
BookmarkNode.fromList(it)
GeckoFetchRequest.fromList(it)
}
}
240.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
BookmarkInfo.fromList(it)
GeckoFetchResponse.fromList(it)
}
}
241.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
SitePermissions.fromList(it)
BookmarkNode.fromList(it)
}
}
242.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
TrackingProtectionException.fromList(it)
BookmarkInfo.fromList(it)
}
}
243.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PwaIcon.fromList(it)
SitePermissions.fromList(it)
}
}
244.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ShareTargetFiles.fromList(it)
TrackingProtectionException.fromList(it)
}
}
245.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ShareTargetParams.fromList(it)
PwaIcon.fromList(it)
}
}
246.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ShareTarget.fromList(it)
ShareTargetFiles.fromList(it)
}
}
247.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ExternalApplicationResource.fromList(it)
ShareTargetParams.fromList(it)
}
}
248.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PwaManifest.fromList(it)
ShareTarget.fromList(it)
}
}
249.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ExternalApplicationResource.fromList(it)
}
}
250.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PwaManifest.fromList(it)
}
}
251.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
SandboxCaptureEntry.fromList(it)
}
@@ -6497,66 +6619,74 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
stream.write(234)
writeValue(stream, value.toList())
}
is ContainerSiteAssignment -> {
is GeckoProxySettings -> {
stream.write(235)
writeValue(stream, value.toList())
}
is GeckoHeader -> {
is ContainerSiteAssignment -> {
stream.write(236)
writeValue(stream, value.toList())
}
is GeckoFetchRequest -> {
is ProxyLoadError -> {
stream.write(237)
writeValue(stream, value.toList())
}
is GeckoFetchResponse -> {
is GeckoHeader -> {
stream.write(238)
writeValue(stream, value.toList())
}
is BookmarkNode -> {
is GeckoFetchRequest -> {
stream.write(239)
writeValue(stream, value.toList())
}
is BookmarkInfo -> {
is GeckoFetchResponse -> {
stream.write(240)
writeValue(stream, value.toList())
}
is SitePermissions -> {
is BookmarkNode -> {
stream.write(241)
writeValue(stream, value.toList())
}
is TrackingProtectionException -> {
is BookmarkInfo -> {
stream.write(242)
writeValue(stream, value.toList())
}
is PwaIcon -> {
is SitePermissions -> {
stream.write(243)
writeValue(stream, value.toList())
}
is ShareTargetFiles -> {
is TrackingProtectionException -> {
stream.write(244)
writeValue(stream, value.toList())
}
is ShareTargetParams -> {
is PwaIcon -> {
stream.write(245)
writeValue(stream, value.toList())
}
is ShareTarget -> {
is ShareTargetFiles -> {
stream.write(246)
writeValue(stream, value.toList())
}
is ExternalApplicationResource -> {
is ShareTargetParams -> {
stream.write(247)
writeValue(stream, value.toList())
}
is PwaManifest -> {
is ShareTarget -> {
stream.write(248)
writeValue(stream, value.toList())
}
is SandboxCaptureEntry -> {
is ExternalApplicationResource -> {
stream.write(249)
writeValue(stream, value.toList())
}
is PwaManifest -> {
stream.write(250)
writeValue(stream, value.toList())
}
is SandboxCaptureEntry -> {
stream.write(251)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
@@ -8279,6 +8409,11 @@ interface GeckoContainerProxyApi {
fun setProxyPort(port: Long)
fun addContainerProxy(contextId: String)
fun removeContainerProxy(contextId: String)
fun upsertProxy(proxy: GeckoProxySettings)
fun removeProxy(proxyId: String)
fun setContainerProxy(contextId: String, proxyId: String)
fun clearContainerProxy(contextId: String)
fun removeContainerProxyRelation(contextId: String, proxyId: String)
fun setSiteAssignments(assignments: Map<String, String>)
fun healthcheck(callback: (Result<Boolean>) -> Unit)
@@ -8345,6 +8480,98 @@ interface GeckoContainerProxyApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.upsertProxy$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val proxyArg = args[0] as GeckoProxySettings
val wrapped: List<Any?> = try {
api.upsertProxy(proxyArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.removeProxy$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val proxyIdArg = args[0] as String
val wrapped: List<Any?> = try {
api.removeProxy(proxyIdArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setContainerProxy$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val contextIdArg = args[0] as String
val proxyIdArg = args[1] as String
val wrapped: List<Any?> = try {
api.setContainerProxy(contextIdArg, proxyIdArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.clearContainerProxy$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val contextIdArg = args[0] as String
val wrapped: List<Any?> = try {
api.clearContainerProxy(contextIdArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.removeContainerProxyRelation$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val contextIdArg = args[0] as String
val proxyIdArg = args[1] as String
val wrapped: List<Any?> = try {
api.removeContainerProxyRelation(contextIdArg, proxyIdArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setSiteAssignments$separatedMessageChannelSuffix", codec)
if (api != null) {
@@ -8785,6 +9012,23 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onProxyLoadError(sequenceArg: Long, detailsArg: ProxyLoadError, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onProxyLoadError$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(sequenceArg, detailsArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(GeckoPigeonUtils.createConnectionError(channelName)))
}
}
}
fun onMlProgress(sequenceArg: Long, progressArg: MlProgressData, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""