mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-17 21:24:48 +03:00
feat: multi-edge fronting_groups + rename google_only to direct
This commit is contained in:
@@ -64,14 +64,18 @@ enum class UiLang { AUTO, FA, EN }
|
||||
*
|
||||
* - [APPS_SCRIPT] (default) — full DPI bypass through the user's deployed
|
||||
* Apps Script relay. Requires a Deployment ID + Auth key.
|
||||
* - [GOOGLE_ONLY] — bootstrap mode. Only the SNI-rewrite tunnel to the
|
||||
* Google edge is active, so the user can reach `script.google.com` to
|
||||
* deploy Code.gs in the first place. No Deployment ID / Auth key needed.
|
||||
* Non-Google traffic goes direct (no relay).
|
||||
* - [DIRECT] — no Apps Script relay. Only the SNI-rewrite tunnel is
|
||||
* active: Google edge by default, plus any user-configured
|
||||
* `fronting_groups` (Vercel, Fastly, …). Useful as a bootstrap to
|
||||
* reach `script.google.com` and deploy Code.gs, or as a standalone
|
||||
* mode for users who only need fronting-group targets. No Deployment
|
||||
* ID / Auth key needed. Non-matching traffic goes raw (no relay).
|
||||
* Was named `GOOGLE_ONLY` before fronting_groups was added — the
|
||||
* string `"google_only"` is still accepted on parse for back-compat.
|
||||
* - [FULL] — full tunnel mode. ALL traffic is tunneled end-to-end through
|
||||
* Apps Script + a remote tunnel node. No certificate installation needed.
|
||||
*/
|
||||
enum class Mode { APPS_SCRIPT, GOOGLE_ONLY, FULL }
|
||||
enum class Mode { APPS_SCRIPT, DIRECT, FULL }
|
||||
|
||||
data class MhrvConfig(
|
||||
val mode: Mode = Mode.APPS_SCRIPT,
|
||||
@@ -177,14 +181,14 @@ data class MhrvConfig(
|
||||
// "missing field `mode`" and startProxy silently returns 0.
|
||||
put("mode", when (mode) {
|
||||
Mode.APPS_SCRIPT -> "apps_script"
|
||||
Mode.GOOGLE_ONLY -> "google_only"
|
||||
Mode.DIRECT -> "direct"
|
||||
Mode.FULL -> "full"
|
||||
})
|
||||
put("listen_host", listenHost)
|
||||
put("listen_port", listenPort)
|
||||
socks5Port?.let { put("socks5_port", it) }
|
||||
|
||||
// In google_only mode these are unused by the Rust side, but we
|
||||
// In direct mode these are unused by the Rust side, but we
|
||||
// still persist whatever the user typed so flipping back to
|
||||
// apps_script mode doesn't wipe their settings.
|
||||
put("script_ids", JSONArray().apply { ids.forEach { put(it) } })
|
||||
@@ -286,7 +290,7 @@ object ConfigStore {
|
||||
// Always include essential fields.
|
||||
obj.put("mode", when (cfg.mode) {
|
||||
Mode.APPS_SCRIPT -> "apps_script"
|
||||
Mode.GOOGLE_ONLY -> "google_only"
|
||||
Mode.DIRECT -> "direct"
|
||||
Mode.FULL -> "full"
|
||||
})
|
||||
val ids = cfg.appsScriptUrls.mapNotNull { url ->
|
||||
@@ -391,7 +395,10 @@ object ConfigStore {
|
||||
|
||||
return MhrvConfig(
|
||||
mode = when (obj.optString("mode", "apps_script")) {
|
||||
"google_only" -> Mode.GOOGLE_ONLY
|
||||
"direct" -> Mode.DIRECT
|
||||
// Deprecated alias kept forever for back-compat with
|
||||
// configs written before the rename.
|
||||
"google_only" -> Mode.DIRECT
|
||||
"full" -> Mode.FULL
|
||||
else -> Mode.APPS_SCRIPT
|
||||
},
|
||||
|
||||
@@ -104,10 +104,10 @@ class MhrvVpnService : VpnService() {
|
||||
startForeground(NOTIF_ID, buildNotif(cfg.listenPort, notifSocks5Port))
|
||||
|
||||
// Deployment ID + auth key are required for apps_script and full
|
||||
// modes — both talk to Apps Script. Only google_only (bootstrap)
|
||||
// runs without them. Closes #73 regression where google_only
|
||||
// users hit this branch and crashed on startForeground timeout.
|
||||
val needsCreds = cfg.mode != Mode.GOOGLE_ONLY
|
||||
// modes — both talk to Apps Script. Only `direct` mode runs
|
||||
// without them. Closes #73 regression where direct-mode users
|
||||
// hit this branch and crashed on startForeground timeout.
|
||||
val needsCreds = cfg.mode != Mode.DIRECT
|
||||
if (needsCreds && (!cfg.hasDeploymentId || cfg.authKey.isBlank())) {
|
||||
Log.e(TAG, "Config is incomplete — deployment ID + auth key required for ${cfg.mode}")
|
||||
try { stopForeground(STOP_FOREGROUND_REMOVE) } catch (_: Throwable) {}
|
||||
|
||||
@@ -83,7 +83,7 @@ object Native {
|
||||
* Live traffic/usage counters for a running proxy handle. Returns a
|
||||
* JSON blob with the StatsSnapshot fields — or an empty string if the
|
||||
* handle is unknown or the proxy isn't using the Apps Script relay
|
||||
* (google_only / full-only modes).
|
||||
* (direct / full-only modes).
|
||||
*
|
||||
* Schema (all integer fields unless noted):
|
||||
* relay_calls, relay_failures, coalesced, bytes_relayed,
|
||||
|
||||
@@ -264,7 +264,7 @@ private fun ImportConfirmDialog(
|
||||
val preview = ids.take(3).joinToString("\n") { " ${it.take(20)}…" }
|
||||
val modeLabel = when (cfg.mode) {
|
||||
com.therealaleph.mhrv.Mode.APPS_SCRIPT -> "apps_script"
|
||||
com.therealaleph.mhrv.Mode.GOOGLE_ONLY -> "google_only"
|
||||
com.therealaleph.mhrv.Mode.DIRECT -> "direct"
|
||||
com.therealaleph.mhrv.Mode.FULL -> "full"
|
||||
}
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ fun HomeScreen(
|
||||
}
|
||||
},
|
||||
enabled = (isVpnRunning ||
|
||||
cfg.mode == Mode.GOOGLE_ONLY ||
|
||||
cfg.mode == Mode.DIRECT ||
|
||||
(cfg.hasDeploymentId && cfg.authKey.isNotBlank())) && !transitioning,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (isVpnRunning) ErrRed else OkGreen,
|
||||
@@ -837,7 +837,7 @@ private fun DeploymentIdsField(
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Mode dropdown: apps_script (default) vs google_only (bootstrap).
|
||||
// Mode dropdown: apps_script (default), direct (no relay), or full.
|
||||
// =========================================================================
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -847,11 +847,11 @@ private fun ModeDropdown(
|
||||
onChange: (Mode) -> Unit,
|
||||
) {
|
||||
val labelApps = "Apps Script (MITM)"
|
||||
val labelGoogle = "Google-only (bootstrap)"
|
||||
val labelDirect = "Direct (no relay)"
|
||||
val labelFull = "Full tunnel (no cert)"
|
||||
val currentLabel = when (mode) {
|
||||
Mode.APPS_SCRIPT -> labelApps
|
||||
Mode.GOOGLE_ONLY -> labelGoogle
|
||||
Mode.DIRECT -> labelDirect
|
||||
Mode.FULL -> labelFull
|
||||
}
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
@@ -878,8 +878,8 @@ private fun ModeDropdown(
|
||||
onClick = { onChange(Mode.APPS_SCRIPT); expanded = false },
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(labelGoogle) },
|
||||
onClick = { onChange(Mode.GOOGLE_ONLY); expanded = false },
|
||||
text = { Text(labelDirect) },
|
||||
onClick = { onChange(Mode.DIRECT); expanded = false },
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(labelFull) },
|
||||
@@ -891,8 +891,8 @@ private fun ModeDropdown(
|
||||
val help = when (mode) {
|
||||
Mode.APPS_SCRIPT ->
|
||||
"Full DPI bypass through your deployed Apps Script relay."
|
||||
Mode.GOOGLE_ONLY ->
|
||||
"Bootstrap: reach *.google.com directly so you can open script.google.com and deploy Code.gs. Non-Google traffic goes direct."
|
||||
Mode.DIRECT ->
|
||||
"SNI-rewrite tunnel only — no relay. Reach *.google.com (and any configured fronting_groups) directly. Useful as a bootstrap to open script.google.com and deploy Code.gs."
|
||||
Mode.FULL ->
|
||||
"All traffic tunneled end-to-end through Apps Script + remote tunnel node. No certificate needed."
|
||||
}
|
||||
@@ -1430,7 +1430,7 @@ private fun CollapsibleSection(
|
||||
* this device relayed.
|
||||
*
|
||||
* Hidden when the handle is 0 (proxy not running) or the JSON comes back
|
||||
* empty (google_only / full-only configs don't run a DomainFronter and so
|
||||
* empty (direct / full-only configs don't run a DomainFronter and so
|
||||
* have nothing to report).
|
||||
*/
|
||||
@Composable
|
||||
|
||||
Reference in New Issue
Block a user