mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 00:11:19 +00:00
Rework Android gateway onboarding setup (#98752)
* feat(android): rework gateway onboarding setup * fix(android): restore protocol mismatch onboarding guidance * fix(android): sync onboarding native i18n * fix(android): preserve LAN manual setup prefill * fix(android): refine onboarding node approval step * fix(android): polish onboarding recovery actions * test(android): cover onboarding protocol mismatch copy * fix(android): separate onboarding node approval * fix(android): guard onboarding node approval transitions * fix(android): keep onboarding approval states reachable * fix(android): wait for node access before onboarding continue * fix(android): refresh node approval after operator handoff * fix(android): refresh onboarding approval states * fix(android): require approval after onboarding permission changes * fix(android): keep onboarding approval gates active * fix(android): preserve permission reapproval state * fix(android): skip node approval on legacy gateways * fix(android): wait for node approval refresh * fix(android): preserve camera setting on upgrade * fix(android): avoid stuck approval check spinner * fix(android): request talk secrets on operator connects * fix(android): avoid missed node approval completion * fix(android): keep nearby LAN setup local * fix(android): complete onboarding after node approval * fix(android): reconcile onboarding with gateway auth plans * chore(android): refresh native i18n inventory after rebase * Fix Android onboarding review edge cases * Fix native i18n onboarding sentinels
This commit is contained in:
parent
776b34a4e6
commit
64318d7624
48 changed files with 3849 additions and 1635 deletions
|
|
@ -179,7 +179,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "ui-state-text",
|
||||
"line": 498,
|
||||
"line": 499,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
|
||||
"source": "Offline",
|
||||
"surface": "android",
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1994,
|
||||
"line": 2007,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
|
||||
"source": "Failed: this host requires wss:// or Tailscale Serve. No TLS endpoint detected.",
|
||||
"surface": "android",
|
||||
|
|
@ -195,7 +195,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1996,
|
||||
"line": 2009,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
|
||||
"source": "Failed: secure endpoint reached, but TLS fingerprint verification timed out. Check Tailscale Serve or gateway TLS and retry.",
|
||||
"surface": "android",
|
||||
|
|
@ -203,7 +203,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1998,
|
||||
"line": 2011,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
|
||||
"source": "Failed: couldn't reach the secure gateway endpoint for this host.",
|
||||
"surface": "android",
|
||||
|
|
@ -371,7 +371,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1095,
|
||||
"line": 1096,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt",
|
||||
"source": "Connecting…",
|
||||
"surface": "android",
|
||||
|
|
@ -379,7 +379,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1095,
|
||||
"line": 1096,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt",
|
||||
"source": "Reconnecting…",
|
||||
"surface": "android",
|
||||
|
|
@ -1195,7 +1195,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 290,
|
||||
"line": 292,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt",
|
||||
"source": "Setup code points to an insecure remote gateway. $remoteGatewaySecurityRule $remoteGatewaySecurityFix",
|
||||
"surface": "android",
|
||||
|
|
@ -1203,7 +1203,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 292,
|
||||
"line": 294,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt",
|
||||
"source": "QR code points to an insecure remote gateway. $remoteGatewaySecurityRule $remoteGatewaySecurityFix",
|
||||
"surface": "android",
|
||||
|
|
@ -1211,7 +1211,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 294,
|
||||
"line": 296,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt",
|
||||
"source": "$remoteGatewaySecurityRule $remoteGatewaySecurityFix",
|
||||
"surface": "android",
|
||||
|
|
@ -1219,7 +1219,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 299,
|
||||
"line": 301,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt",
|
||||
"source": "Setup code has invalid gateway URL.",
|
||||
"surface": "android",
|
||||
|
|
@ -1227,7 +1227,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 300,
|
||||
"line": 302,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt",
|
||||
"source": "QR code did not contain a valid setup code.",
|
||||
"surface": "android",
|
||||
|
|
@ -1235,7 +1235,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 301,
|
||||
"line": 303,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt",
|
||||
"source": "Enter a valid manual endpoint to connect.",
|
||||
"surface": "android",
|
||||
|
|
@ -1666,48 +1666,240 @@
|
|||
"id": "native.android.0bbe0d733b1328fd"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 437,
|
||||
"kind": "ui-call",
|
||||
"line": 654,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "OPENCLAW",
|
||||
"source": "Trust",
|
||||
"surface": "android",
|
||||
"id": "native.android.d42fe72f8e76b450"
|
||||
"id": "native.android.9ac2d4498b17d478"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 659,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Cancel",
|
||||
"surface": "android",
|
||||
"id": "native.android.e26ee07a1d1911be"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 442,
|
||||
"line": 856,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Your personal AI assistant.\nExfoliate! Exfoliate!",
|
||||
"source": "Welcome to OpenClaw",
|
||||
"surface": "android",
|
||||
"id": "native.android.a5c2d7f27576f0c4"
|
||||
"id": "native.android.64c8db13ef91513c"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 452,
|
||||
"line": 938,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Connect to your Gateway",
|
||||
"surface": "android",
|
||||
"id": "native.android.cc878f72a6b70b23"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 939,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Choose device permissions",
|
||||
"surface": "android",
|
||||
"id": "native.android.bf3750bf264f0063"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 940,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Use OpenClaw from your phone",
|
||||
"surface": "android",
|
||||
"id": "native.android.3bfe29d4d13d3c3e"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 962,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Security notice",
|
||||
"surface": "android",
|
||||
"id": "native.android.c0b539eeb4612f5d"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 964,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "The connected OpenClaw agent can use device capabilities you enable. Continue only if you trust the Gateway and agent you connect to.",
|
||||
"surface": "android",
|
||||
"id": "native.android.5aed4b5c8d6b246d"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1005,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Connect Gateway",
|
||||
"surface": "android",
|
||||
"id": "native.android.63c31ee564d63347"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 596,
|
||||
"kind": "ui-toast",
|
||||
"line": 1015,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Setup code issue",
|
||||
"source": "Could not open setup guide.",
|
||||
"surface": "android",
|
||||
"id": "native.android.afdd3e2c10f56b75"
|
||||
"id": "native.android.58d80589d9d4b7ff"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 613,
|
||||
"line": 1023,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Advanced",
|
||||
"source": "Scan QR or setup code",
|
||||
"surface": "android",
|
||||
"id": "native.android.084eda7046e91737"
|
||||
"id": "native.android.18d6dd32cfcc1c9a"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 622,
|
||||
"line": 1029,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Set up manually",
|
||||
"surface": "android",
|
||||
"id": "native.android.8b8acc5b98b8c99f"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1051,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Before you start",
|
||||
"surface": "android",
|
||||
"id": "native.android.debd7b3e59db6ab2"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1057,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Access to the Gateway device",
|
||||
"surface": "android",
|
||||
"id": "native.android.9cfcf68aae2c05c6"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1061,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Phone can reach the Gateway",
|
||||
"surface": "android",
|
||||
"id": "native.android.71ce00f359981a0b"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1068,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Android setup guide",
|
||||
"surface": "android",
|
||||
"id": "native.android.68a31cee94a9db86"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1122,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Setup Gateway",
|
||||
"surface": "android",
|
||||
"id": "native.android.d514a981f80779e9"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1128,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Start your Gateway.",
|
||||
"surface": "android",
|
||||
"id": "native.android.6a45e2dae35ab5a7"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1134,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Generate a QR code.",
|
||||
"surface": "android",
|
||||
"id": "native.android.c6f9da7008df05a6"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1155,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Choose from gallery",
|
||||
"surface": "android",
|
||||
"id": "native.android.96d40475de2885d6"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1209,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "QR code not accepted",
|
||||
"surface": "android",
|
||||
"id": "native.android.fd83bd36419ef526"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1224,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Choose another image",
|
||||
"surface": "android",
|
||||
"id": "native.android.4dde43318559c65d"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1277,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Scan QR code",
|
||||
"surface": "android",
|
||||
"id": "native.android.54bab6e4054484d0"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1279,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Open the camera and frame the code from openclaw qr.",
|
||||
"surface": "android",
|
||||
"id": "native.android.05987e822ac102ec"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1313,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Align the QR code inside the square.",
|
||||
"surface": "android",
|
||||
"id": "native.android.90fa932d2e0adbd9"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1331,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Camera access is needed to scan the setup QR.",
|
||||
"surface": "android",
|
||||
"id": "native.android.7a817ddf54373dbf"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1336,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Allow camera",
|
||||
"surface": "android",
|
||||
"id": "native.android.c8d5f12cb28f3853"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1357,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Close scanner",
|
||||
"surface": "android",
|
||||
"id": "native.android.7efba153567b1232"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1519,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Enter setup code",
|
||||
"surface": "android",
|
||||
"id": "native.android.b8d5431e1e441f74"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1520,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Setup code",
|
||||
"surface": "android",
|
||||
|
|
@ -1715,7 +1907,47 @@
|
|||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 624,
|
||||
"line": 1524,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Paste setup code",
|
||||
"surface": "android",
|
||||
"id": "native.android.06a2214da1eee841"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1528,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Setup code was not accepted",
|
||||
"surface": "android",
|
||||
"id": "native.android.35aec8a97ac12e1c"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1532,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Use setup code",
|
||||
"surface": "android",
|
||||
"id": "native.android.9aaee5a05de93dbe"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1563,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Manual setup",
|
||||
"surface": "android",
|
||||
"id": "native.android.073c93545602b198"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1566,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Gateway URL",
|
||||
"surface": "android",
|
||||
"id": "native.android.46d58ca58c32ad52"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1568,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Host",
|
||||
"surface": "android",
|
||||
|
|
@ -1723,47 +1955,55 @@
|
|||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 625,
|
||||
"line": 1569,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Port",
|
||||
"surface": "android",
|
||||
"id": "native.android.e7f368b47b705410"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 628,
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1572,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "TLS off",
|
||||
"source": "Use the Gateway computer's LAN address or secure remote hostname.",
|
||||
"surface": "android",
|
||||
"id": "native.android.20d94e1cedd3f319"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 628,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "TLS on",
|
||||
"surface": "android",
|
||||
"id": "native.android.a3a158b4a10d34ba"
|
||||
"id": "native.android.1b0d9893ae4c0682"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 629,
|
||||
"line": 1579,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Local",
|
||||
"source": "Token",
|
||||
"surface": "android",
|
||||
"id": "native.android.a02f48c59c8b7b9d"
|
||||
"id": "native.android.bfc48711d6deed92"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 631,
|
||||
"line": 1580,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Token optional",
|
||||
"source": "Paste token",
|
||||
"surface": "android",
|
||||
"id": "native.android.aae4212f2ff4df98"
|
||||
"id": "native.android.7ac2b9aec9d78cda"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 632,
|
||||
"line": 1582,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Paste a shared Gateway token or operator-issued token.",
|
||||
"surface": "android",
|
||||
"id": "native.android.96ba0e3919c3955a"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1589,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Password",
|
||||
"surface": "android",
|
||||
"id": "native.android.4a5a1702f0130328"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1590,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Password optional",
|
||||
"surface": "android",
|
||||
|
|
@ -1771,39 +2011,159 @@
|
|||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 637,
|
||||
"line": 1594,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Pair with Gateway",
|
||||
"source": "Connection type",
|
||||
"surface": "android",
|
||||
"id": "native.android.369ee061f6d7881f"
|
||||
"id": "native.android.1259d0d0d1671ed7"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 710,
|
||||
"line": 1596,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Last gateway",
|
||||
"source": "Local network",
|
||||
"surface": "android",
|
||||
"id": "native.android.82b296ed39647687"
|
||||
"id": "native.android.278d9abb3abc12e6"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 775,
|
||||
"line": 1597,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Edit connection",
|
||||
"source": "Secure remote",
|
||||
"surface": "android",
|
||||
"id": "native.android.e0e5d2bb055863fb"
|
||||
"id": "native.android.4aa2b8be11a971eb"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 778,
|
||||
"line": 1600,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Copy diagnostic",
|
||||
"source": "Local works for LAN or emulator hosts. Secure remote is for wss:// or Tailscale Serve/Funnel.",
|
||||
"surface": "android",
|
||||
"id": "native.android.ea2f18dad7749613"
|
||||
"id": "native.android.8c31e2762b496d61"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 825,
|
||||
"line": 1608,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Could not test connection",
|
||||
"surface": "android",
|
||||
"id": "native.android.24a0a4819f223b87"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1613,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Test connection",
|
||||
"surface": "android",
|
||||
"id": "native.android.9a8f8ba539d4dd68"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 1791,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "View details",
|
||||
"surface": "android",
|
||||
"id": "native.android.a1c6ad798cdb4e32"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 1824,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Connection details",
|
||||
"surface": "android",
|
||||
"id": "native.android.ebdc41fd34eab0a5"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 1836,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Copy",
|
||||
"surface": "android",
|
||||
"id": "native.android.f982d1f6ad7b3e5d"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 1841,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Close",
|
||||
"surface": "android",
|
||||
"id": "native.android.fa2a2369f23aa195"
|
||||
},
|
||||
{
|
||||
"kind": "ui-toast",
|
||||
"line": 1853,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Details copied",
|
||||
"surface": "android",
|
||||
"id": "native.android.50cca1e186823cd6"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1938,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Approve node access",
|
||||
"surface": "android",
|
||||
"id": "native.android.31a4bf3ed4071f7c"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1945,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Gateway pairing is complete. Approve this phone as a node so OpenClaw can use the device capabilities you enable.",
|
||||
"surface": "android",
|
||||
"id": "native.android.f43cb6ed03dc6766"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1953,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "On the Gateway computer, run:",
|
||||
"surface": "android",
|
||||
"id": "native.android.8e49feba12c350ac"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1962,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Use the requestId from the pending command in the approve command.",
|
||||
"surface": "android",
|
||||
"id": "native.android.6189b5e2af7cc793"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1973,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "I have approved",
|
||||
"surface": "android",
|
||||
"id": "native.android.53bcd45eeb574d74"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1986,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Still waiting for approval",
|
||||
"surface": "android",
|
||||
"id": "native.android.cdc44aaeb8bd6e4a"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1989,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Run the approve command on the Gateway computer, then check again.",
|
||||
"surface": "android",
|
||||
"id": "native.android.df5cc00eb95c21ad"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1996,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "OK",
|
||||
"surface": "android",
|
||||
"id": "native.android.f579f91c216dd6c4"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 2146,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Copy approval command",
|
||||
"surface": "android",
|
||||
|
|
@ -1811,39 +2171,23 @@
|
|||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 852,
|
||||
"line": 2172,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Allow permissions",
|
||||
"source": "Only enable access you are comfortable letting OpenClaw use while this phone is connected. You can change these later in Android Settings.",
|
||||
"surface": "android",
|
||||
"id": "native.android.a31f2beb68298aa8"
|
||||
"id": "native.android.d4c3bcf880727fb3"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 857,
|
||||
"line": 2184,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "These permissions keep OpenClaw secure\nand useful.",
|
||||
"source": "Continue",
|
||||
"surface": "android",
|
||||
"id": "native.android.b82104d8df1d4b4a"
|
||||
"id": "native.android.d7ea800cc2475013"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 935,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Open $title",
|
||||
"surface": "android",
|
||||
"id": "native.android.03b95088ba72bd8e"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 996,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Choose what this phone can share with OpenClaw. You can change these later in Settings.",
|
||||
"surface": "android",
|
||||
"id": "native.android.f38ca3445453a6bb"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1017,
|
||||
"line": 2211,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Back",
|
||||
"surface": "android",
|
||||
|
|
@ -1851,71 +2195,23 @@
|
|||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1021,
|
||||
"line": 2258,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Permission Setup",
|
||||
"source": "Permissions",
|
||||
"surface": "android",
|
||||
"id": "native.android.4b54af42d17dec84"
|
||||
"id": "native.android.f3f0527db434d756"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1076,
|
||||
"line": 2583,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Granted",
|
||||
"source": "The code may have expired or been generated for another Gateway.",
|
||||
"surface": "android",
|
||||
"id": "native.android.437599c024df158e"
|
||||
"id": "native.android.29732759e050c874"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1076,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Not granted",
|
||||
"surface": "android",
|
||||
"id": "native.android.cf8344d3562e28e3"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1101,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Continue",
|
||||
"surface": "android",
|
||||
"id": "native.android.d7ea800cc2475013"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1156,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Scan fresh setup code",
|
||||
"surface": "android",
|
||||
"id": "native.android.547f11a819721ffb"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1158,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Retry connection",
|
||||
"surface": "android",
|
||||
"id": "native.android.3b169db8b57795b5"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1337,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Setup code expired. Scan a fresh setup QR.",
|
||||
"surface": "android",
|
||||
"id": "native.android.e44224ed4df1700f"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1344,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Gateway access needs review. Check gateway authentication scopes, then retry.",
|
||||
"surface": "android",
|
||||
"id": "native.android.e03bb4ea463ba7a5"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1345,
|
||||
"line": 2587,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Gateway password is required. Enter it again or edit this connection.",
|
||||
"surface": "android",
|
||||
|
|
@ -1923,7 +2219,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1346,
|
||||
"line": 2588,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Gateway password is invalid. Re-enter it or reset this gateway connection.",
|
||||
"surface": "android",
|
||||
|
|
@ -1931,7 +2227,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1347,
|
||||
"line": 2589,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Gateway token is required. Enter it again or edit this connection.",
|
||||
"surface": "android",
|
||||
|
|
@ -1939,7 +2235,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1349,
|
||||
"line": 2591,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Gateway requires this device identity. Re-authenticate or reset this gateway connection.",
|
||||
"surface": "android",
|
||||
|
|
@ -1947,7 +2243,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1353,
|
||||
"line": 2595,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Saved authentication is invalid. Re-authenticate or reset this gateway connection.",
|
||||
"surface": "android",
|
||||
|
|
@ -1955,15 +2251,15 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1354,
|
||||
"line": 2596,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Gateway authentication is not configured. Configure it on the gateway host, then retry.",
|
||||
"source": "Gateway authentication is not configured. Edit this connection and try again.",
|
||||
"surface": "android",
|
||||
"id": "native.android.3f61f2c34784ed0f"
|
||||
"id": "native.android.214b03b4cd2199b1"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1355,
|
||||
"line": 2597,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Gateway authentication needs review. Check gateway settings, then retry.",
|
||||
"surface": "android",
|
||||
|
|
@ -1971,7 +2267,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1372,
|
||||
"line": 2614,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "$summary $it",
|
||||
"surface": "android",
|
||||
|
|
@ -1979,19 +2275,27 @@
|
|||
},
|
||||
{
|
||||
"kind": "ui-toast",
|
||||
"line": 1419,
|
||||
"line": 2757,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Approval command copied",
|
||||
"surface": "android",
|
||||
"id": "native.android.a7933196a18ec924"
|
||||
},
|
||||
{
|
||||
"kind": "ui-toast",
|
||||
"line": 1446,
|
||||
"kind": "conditional-branch",
|
||||
"line": 2819,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Diagnostic copied",
|
||||
"source": "Granted",
|
||||
"surface": "android",
|
||||
"id": "native.android.bc9715de99c769c7"
|
||||
"id": "native.android.437599c024df158e"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 2819,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt",
|
||||
"source": "Not granted",
|
||||
"surface": "android",
|
||||
"id": "native.android.cf8344d3562e28e3"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
|
|
@ -3929,6 +4233,14 @@
|
|||
"surface": "android",
|
||||
"id": "native.android.f23eab3d31c5e25c"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1213,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
|
||||
"source": "Open ${row.title}",
|
||||
"surface": "android",
|
||||
"id": "native.android.8452bcda8a619361"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1287,
|
||||
|
|
@ -4105,17 +4417,9 @@
|
|||
"surface": "android",
|
||||
"id": "native.android.45005deba537cc59"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 1772,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
|
||||
"source": "Open ${row.title}",
|
||||
"surface": "android",
|
||||
"id": "native.android.8452bcda8a619361"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 1791,
|
||||
"line": 1793,
|
||||
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
|
||||
"source": "Main session",
|
||||
"surface": "android",
|
||||
|
|
@ -5445,98 +5749,18 @@
|
|||
"kind": "resource-string",
|
||||
"line": 25,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Gateway Setup",
|
||||
"surface": "android",
|
||||
"id": "native.android.3d9d01cc0af8727d"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 26,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Connect to your Gateway",
|
||||
"surface": "android",
|
||||
"id": "native.android.fff6a6f130e88354"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 27,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Scan setup code",
|
||||
"surface": "android",
|
||||
"id": "native.android.77a54beda22bfeb7"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 28,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Use your Gateway QR or setup code",
|
||||
"surface": "android",
|
||||
"id": "native.android.8f525f9218215b42"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 29,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Nearby gateway",
|
||||
"surface": "android",
|
||||
"id": "native.android.40da32c36971b959"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 30,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Enter gateway URL",
|
||||
"surface": "android",
|
||||
"id": "native.android.7423bd0086e6897e"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 31,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Connect using a manual URL",
|
||||
"surface": "android",
|
||||
"id": "native.android.c9420b6801f5a76f"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 32,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Permissions",
|
||||
"surface": "android",
|
||||
"id": "native.android.4b55356d9bf6e444"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 33,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Done",
|
||||
"surface": "android",
|
||||
"id": "native.android.c49a911b95af5d5d"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 34,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Verify the certificate fingerprint before trusting this gateway.\n\n%1$s",
|
||||
"surface": "android",
|
||||
"id": "native.android.288b83f93172c633"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 35,
|
||||
"line": 26,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "The gateway certificate changed. Continue only if you expected this.\n\nOld SHA-256:\n%1$s\n\nNew SHA-256:\n%2$s",
|
||||
"surface": "android",
|
||||
"id": "native.android.6ad2d3f084142bed"
|
||||
},
|
||||
{
|
||||
"kind": "resource-string",
|
||||
"line": 36,
|
||||
"path": "apps/android/app/src/main/res/values/strings.xml",
|
||||
"source": "Gateway Recovery",
|
||||
"surface": "android",
|
||||
"id": "native.android.6ffa3d752514836d"
|
||||
},
|
||||
{
|
||||
"kind": "plist-string",
|
||||
"line": 8,
|
||||
|
|
|
|||
|
|
@ -230,8 +230,9 @@ dependencies {
|
|||
implementation(libs.androidx.camera.core)
|
||||
implementation(libs.androidx.camera.camera2)
|
||||
implementation(libs.androidx.camera.lifecycle)
|
||||
implementation(libs.androidx.camera.view)
|
||||
implementation(libs.androidx.camera.video)
|
||||
implementation(libs.play.services.code.scanner)
|
||||
implementation(libs.barcode.scanning)
|
||||
|
||||
// Unicast DNS-SD (Wide-Area Bonjour) for tailnet discovery domains.
|
||||
implementation(libs.dnsjava)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import ai.openclaw.app.chat.ChatMessage
|
|||
import ai.openclaw.app.chat.ChatPendingToolCall
|
||||
import ai.openclaw.app.chat.ChatSessionEntry
|
||||
import ai.openclaw.app.chat.OutgoingAttachment
|
||||
import ai.openclaw.app.gateway.DeviceAuthEntry
|
||||
import ai.openclaw.app.gateway.DeviceAuthStore
|
||||
import ai.openclaw.app.gateway.DeviceIdentityStore
|
||||
import ai.openclaw.app.gateway.GatewayDiscovery
|
||||
|
|
@ -760,6 +761,9 @@ class NodeRuntime(
|
|||
updateStatus {
|
||||
nodeConnectionProblem = gatewayConnectionProblem(error, pauseReconnect)
|
||||
}
|
||||
if (operatorConnected && nodeConnectFailureNeedsApprovalRefresh(error)) {
|
||||
scope.launch { refreshNodesDevicesFromGateway() }
|
||||
}
|
||||
},
|
||||
onEvent = { _, _ -> },
|
||||
onInvoke = { req ->
|
||||
|
|
@ -1851,10 +1855,13 @@ class NodeRuntime(
|
|||
) {
|
||||
activeGatewayAuth = auth
|
||||
val tls = connectionManager.resolveTlsParams(endpoint)
|
||||
val storedOperatorEntry = loadStoredRoleDeviceAuthEntry("operator")
|
||||
val usesStoredOperatorDeviceToken =
|
||||
operatorSessionUsesStoredDeviceToken(auth, storedOperatorEntry?.token)
|
||||
val operatorAuth =
|
||||
resolveOperatorSessionConnectAuth(
|
||||
auth = auth,
|
||||
storedOperatorToken = loadStoredRoleDeviceToken("operator"),
|
||||
storedOperatorToken = storedOperatorEntry?.token,
|
||||
)
|
||||
if (operatorAuth == null) {
|
||||
updateStatus {
|
||||
|
|
@ -1869,7 +1876,13 @@ class NodeRuntime(
|
|||
operatorAuth.token,
|
||||
operatorAuth.bootstrapToken,
|
||||
operatorAuth.password,
|
||||
connectionManager.buildOperatorConnectOptions(),
|
||||
connectionManager.buildOperatorConnectOptions(
|
||||
scopes =
|
||||
operatorConnectScopesForAuth(
|
||||
usesStoredDeviceToken = usesStoredOperatorDeviceToken,
|
||||
storedOperatorScopes = storedOperatorEntry?.scopes,
|
||||
),
|
||||
),
|
||||
tls,
|
||||
)
|
||||
}
|
||||
|
|
@ -2015,9 +2028,9 @@ class NodeRuntime(
|
|||
connect(GatewayEndpoint.manual(host = host, port = port))
|
||||
}
|
||||
|
||||
private fun loadStoredRoleDeviceToken(role: String): String? {
|
||||
private fun loadStoredRoleDeviceAuthEntry(role: String): DeviceAuthEntry? {
|
||||
val deviceId = identityStore.loadOrCreate().deviceId
|
||||
return deviceAuthStore.loadToken(deviceId, role)
|
||||
return deviceAuthStore.loadEntry(deviceId, role)
|
||||
}
|
||||
|
||||
private fun maybeStartOperatorSessionAfterNodeConnect(
|
||||
|
|
@ -2027,10 +2040,13 @@ class NodeRuntime(
|
|||
if (operatorConnected) {
|
||||
return
|
||||
}
|
||||
val storedOperatorEntry = loadStoredRoleDeviceAuthEntry("operator")
|
||||
val usesStoredOperatorDeviceToken =
|
||||
operatorSessionUsesStoredDeviceToken(auth, storedOperatorEntry?.token)
|
||||
val operatorAuth =
|
||||
resolveOperatorSessionConnectAuth(
|
||||
auth = auth,
|
||||
storedOperatorToken = loadStoredRoleDeviceToken("operator"),
|
||||
storedOperatorToken = storedOperatorEntry?.token,
|
||||
) ?: return
|
||||
updateStatus {
|
||||
operatorStatusText = "Connecting…"
|
||||
|
|
@ -2041,7 +2057,13 @@ class NodeRuntime(
|
|||
operatorAuth.token,
|
||||
operatorAuth.bootstrapToken,
|
||||
operatorAuth.password,
|
||||
connectionManager.buildOperatorConnectOptions(),
|
||||
connectionManager.buildOperatorConnectOptions(
|
||||
scopes =
|
||||
operatorConnectScopesForAuth(
|
||||
usesStoredDeviceToken = usesStoredOperatorDeviceToken,
|
||||
storedOperatorScopes = storedOperatorEntry?.scopes,
|
||||
),
|
||||
),
|
||||
connectionManager.resolveTlsParams(endpoint),
|
||||
)
|
||||
}
|
||||
|
|
@ -2449,6 +2471,7 @@ class NodeRuntime(
|
|||
if (!refreshStarted) return
|
||||
if (!operatorConnected) {
|
||||
nodeApprovalRefreshGuard.publishIfCurrent(refreshGeneration) {
|
||||
_nodeCapabilityApproval.value = GatewayNodeCapabilityApproval.Loading
|
||||
_nodesDevicesSummary.value =
|
||||
GatewayNodesDevicesSummary(
|
||||
nodes = emptyList(),
|
||||
|
|
@ -2462,12 +2485,14 @@ class NodeRuntime(
|
|||
try {
|
||||
val nodesRes = operatorSession.request("node.list", "{}")
|
||||
val nodesRoot = json.parseToJsonElement(nodesRes).asObjectOrNull()
|
||||
val nodes = parseGatewayNodes(nodesRoot?.get("nodes") as? JsonArray)
|
||||
val nodes = parseGatewayNodeList(nodesRoot)
|
||||
val selfNodeId = identityStore.loadOrCreate().deviceId
|
||||
val approval =
|
||||
currentNodeCapabilityApproval(
|
||||
nodes = nodes,
|
||||
selfNodeId = identityStore.loadOrCreate().deviceId,
|
||||
selfNodeId = selfNodeId,
|
||||
)
|
||||
val selfNodeConnected = nodes.firstOrNull { it.id == selfNodeId }?.connected == true
|
||||
val publishedApproval =
|
||||
nodeApprovalRefreshGuard.publishIfCurrent(refreshGeneration) {
|
||||
_nodeCapabilityApproval.value = approval
|
||||
|
|
@ -2475,6 +2500,13 @@ class NodeRuntime(
|
|||
if (!publishedApproval) {
|
||||
return
|
||||
}
|
||||
if (selfNodeConnected && !_nodeConnected.value) {
|
||||
updateStatus {
|
||||
nodeConnectionProblem = null
|
||||
_nodeConnected.value = true
|
||||
nodeStatusText = "Connected"
|
||||
}
|
||||
}
|
||||
scheduleNodeApprovalCommandRefresh(refreshGeneration, approval)
|
||||
val devicesRoot =
|
||||
try {
|
||||
|
|
@ -3409,6 +3441,27 @@ internal fun resolveOperatorSessionConnectAuth(
|
|||
)
|
||||
}
|
||||
|
||||
internal fun operatorSessionUsesStoredDeviceToken(
|
||||
auth: NodeRuntime.GatewayConnectAuth,
|
||||
storedOperatorToken: String?,
|
||||
): Boolean {
|
||||
val storedToken = storedOperatorToken?.trim()?.takeIf { it.isNotEmpty() }
|
||||
if (storedToken == null) return false
|
||||
val explicitToken = auth.token?.trim()?.takeIf { it.isNotEmpty() }
|
||||
val explicitPassword = auth.password?.trim()?.takeIf { it.isNotEmpty() }
|
||||
return explicitToken == null && explicitPassword == null
|
||||
}
|
||||
|
||||
internal fun operatorConnectScopesForAuth(
|
||||
usesStoredDeviceToken: Boolean,
|
||||
storedOperatorScopes: List<String>?,
|
||||
): List<String> {
|
||||
if (usesStoredDeviceToken && storedOperatorScopes != null) {
|
||||
return ConnectionManager.operatorScopesForStoredDeviceToken(storedOperatorScopes)
|
||||
}
|
||||
return ConnectionManager.nativeClientOperatorScopes
|
||||
}
|
||||
|
||||
private enum class HomeCanvasGatewayState {
|
||||
Connected,
|
||||
Connecting,
|
||||
|
|
@ -3572,6 +3625,12 @@ internal fun parseGatewayNodeApprovalState(raw: String?): GatewayNodeApprovalSta
|
|||
else -> GatewayNodeApprovalState.Loading
|
||||
}
|
||||
|
||||
internal fun gatewayEventInvalidatesNodesDevices(event: String): Boolean =
|
||||
event == "node.pair.requested" || event == "node.pair.resolved"
|
||||
|
||||
internal fun nodeConnectFailureNeedsApprovalRefresh(error: GatewaySession.ErrorShape): Boolean =
|
||||
error.details?.code == "PAIRING_REQUIRED"
|
||||
|
||||
internal fun currentNodeCapabilityApproval(
|
||||
nodes: List<GatewayNodeSummary>,
|
||||
selfNodeId: String,
|
||||
|
|
@ -3618,6 +3677,25 @@ internal fun parseGatewayNodeSummary(item: JsonElement): GatewayNodeSummary? {
|
|||
)
|
||||
}
|
||||
|
||||
internal fun parseGatewayNodeList(root: JsonObject?): List<GatewayNodeSummary> {
|
||||
if (root == null) return emptyList()
|
||||
val seen = mutableSetOf<String>()
|
||||
val result = mutableListOf<GatewayNodeSummary>()
|
||||
|
||||
fun append(nodes: JsonArray?) {
|
||||
for (node in nodes?.mapNotNull(::parseGatewayNodeSummary).orEmpty()) {
|
||||
if (seen.add(node.id)) {
|
||||
result.add(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
append(root["nodes"] as? JsonArray)
|
||||
append(root["pending"] as? JsonArray)
|
||||
append(root["paired"] as? JsonArray)
|
||||
return result
|
||||
}
|
||||
|
||||
data class GatewayNodeSummary(
|
||||
val id: String,
|
||||
val displayName: String?,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class SecurePrefs(
|
|||
"notifications.forwarding.maxEventsPerMinute"
|
||||
private const val notificationsForwardingSessionKeyKey = "notifications.forwarding.sessionKey"
|
||||
private const val installedAppsSharingEnabledKey = "device.apps.sharing.enabled"
|
||||
private const val cameraEnabledKey = "camera.enabled"
|
||||
private const val voiceMicEnabledKey = "voice.micEnabled"
|
||||
private const val appearanceThemeModeKey = "appearance.themeMode"
|
||||
}
|
||||
|
|
@ -51,6 +52,7 @@ class SecurePrefs(
|
|||
// Non-secret UI/runtime preferences stay readable for migration and backup behavior.
|
||||
private val plainPrefs: SharedPreferences =
|
||||
appContext.getSharedPreferences(plainPrefsName, Context.MODE_PRIVATE)
|
||||
private val hadPlainPrefsBeforeInit = plainPrefs.all.isNotEmpty()
|
||||
|
||||
// Gateway credentials and arbitrary secret strings are isolated behind EncryptedSharedPreferences.
|
||||
private val masterKey by lazy {
|
||||
|
|
@ -68,7 +70,7 @@ class SecurePrefs(
|
|||
MutableStateFlow(loadOrMigrateDisplayName(context = context))
|
||||
val displayName: StateFlow<String> = _displayName
|
||||
|
||||
private val _cameraEnabled = MutableStateFlow(plainPrefs.getBoolean("camera.enabled", true))
|
||||
private val _cameraEnabled = MutableStateFlow(loadCameraEnabled())
|
||||
val cameraEnabled: StateFlow<Boolean> = _cameraEnabled
|
||||
|
||||
private val _locationMode = MutableStateFlow(loadLocationMode())
|
||||
|
|
@ -199,7 +201,7 @@ class SecurePrefs(
|
|||
}
|
||||
|
||||
fun setCameraEnabled(value: Boolean) {
|
||||
plainPrefs.edit { putBoolean("camera.enabled", value) }
|
||||
plainPrefs.edit { putBoolean(cameraEnabledKey, value) }
|
||||
_cameraEnabled.value = value
|
||||
}
|
||||
|
||||
|
|
@ -572,6 +574,15 @@ class SecurePrefs(
|
|||
return resolved
|
||||
}
|
||||
|
||||
private fun loadCameraEnabled(): Boolean {
|
||||
if (plainPrefs.contains(cameraEnabledKey)) {
|
||||
return plainPrefs.getBoolean(cameraEnabledKey, false)
|
||||
}
|
||||
val migratedValue = hadPlainPrefsBeforeInit
|
||||
plainPrefs.edit { putBoolean(cameraEnabledKey, migratedValue) }
|
||||
return migratedValue
|
||||
}
|
||||
|
||||
private fun loadWakeWords(): List<String> {
|
||||
val raw = plainPrefs.getString("voiceWake.triggerWords", null)?.trim()
|
||||
if (raw.isNullOrEmpty()) return defaultWakeWords
|
||||
|
|
|
|||
|
|
@ -705,6 +705,7 @@ class GatewaySession(
|
|||
setOf(
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.talk.secrets",
|
||||
"operator.write",
|
||||
)
|
||||
scopes.filter { allowedOperatorScopes.contains(it) }.distinct().sorted()
|
||||
|
|
|
|||
|
|
@ -32,6 +32,30 @@ class ConnectionManager(
|
|||
private val manualTls: () -> Boolean,
|
||||
) {
|
||||
companion object {
|
||||
internal val legacyOperatorScopes: List<String> =
|
||||
listOf(
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.write",
|
||||
)
|
||||
|
||||
internal val nativeClientOperatorScopes: List<String> =
|
||||
listOf(
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.talk.secrets",
|
||||
"operator.write",
|
||||
)
|
||||
|
||||
internal fun operatorScopesForStoredDeviceToken(storedScopes: List<String>): List<String> {
|
||||
val normalized =
|
||||
storedScopes
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() }
|
||||
.distinct()
|
||||
return normalized.ifEmpty { legacyOperatorScopes }
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide whether a discovered/manual endpoint must use pinned TLS or can stay local cleartext.
|
||||
*/
|
||||
|
|
@ -187,15 +211,12 @@ class ConnectionManager(
|
|||
)
|
||||
|
||||
/** Connect options for the Android operator session that drives approvals and UI actions. */
|
||||
fun buildOperatorConnectOptions(): GatewayConnectOptions =
|
||||
fun buildOperatorConnectOptions(
|
||||
scopes: List<String> = nativeClientOperatorScopes,
|
||||
): GatewayConnectOptions =
|
||||
GatewayConnectOptions(
|
||||
role = "operator",
|
||||
scopes =
|
||||
listOf(
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.write",
|
||||
),
|
||||
scopes = scopes,
|
||||
caps = emptyList(),
|
||||
commands = emptyList(),
|
||||
permissions = emptyMap(),
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ internal fun resolveGatewayConnectConfig(
|
|||
passwordInput: String,
|
||||
): GatewayConnectConfig? {
|
||||
if (useSetupCode) {
|
||||
val setup = decodeGatewaySetupCode(setupCode) ?: return null
|
||||
val setup = resolveSetupCodeCandidate(setupCode)?.let(::decodeGatewaySetupCode) ?: return null
|
||||
val parsed = parseGatewayEndpointResult(setup.url).config ?: return null
|
||||
val setupBootstrapToken =
|
||||
setup.bootstrapToken
|
||||
|
|
@ -264,6 +264,8 @@ internal fun decodeGatewaySetupCode(rawInput: String): GatewaySetupCode? {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun manualTokenLooksLikeSetupCode(rawInput: String): Boolean = resolveSetupCodeCandidate(rawInput)?.let(::decodeGatewaySetupCode) != null
|
||||
|
||||
/** Resolves QR scanner text to setup-code or validation error for UI copy. */
|
||||
internal fun resolveScannedSetupCodeResult(rawInput: String): GatewayScannedSetupCodeResult {
|
||||
val setupCode =
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1459,9 +1459,9 @@ private fun SettingsShellScreen(
|
|||
}
|
||||
item {
|
||||
SettingsGroup(
|
||||
rows = listOf(SettingsRow("Sign Out", "Disconnect", Icons.AutoMirrored.Filled.ExitToApp)),
|
||||
rows = listOf(SettingsRow("Sign Out", "Return to setup", Icons.AutoMirrored.Filled.ExitToApp)),
|
||||
onOpen = { },
|
||||
onAction = { viewModel.disconnect() },
|
||||
onAction = { viewModel.pairNewGateway() },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1724,6 +1724,7 @@ private fun SettingsGroup(
|
|||
rows.forEachIndexed { index, row ->
|
||||
SettingsListRow(
|
||||
row = row,
|
||||
showDisclosure = row.route != null || onAction != null,
|
||||
onClick = {
|
||||
val rowRoute = row.route
|
||||
if (rowRoute == null) {
|
||||
|
|
@ -1744,6 +1745,7 @@ private fun SettingsGroup(
|
|||
@Composable
|
||||
private fun SettingsListRow(
|
||||
row: SettingsRow,
|
||||
showDisclosure: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -1766,10 +1768,10 @@ private fun SettingsListRow(
|
|||
row.status?.let { active ->
|
||||
Box(modifier = Modifier.size(4.5.dp).clip(CircleShape).background(if (active) ClawTheme.colors.success else ClawTheme.colors.textSubtle))
|
||||
}
|
||||
if (row.route != null) {
|
||||
if (showDisclosure) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = "Open ${row.title}",
|
||||
contentDescription = if (row.route != null) "Open ${row.title}" else row.title,
|
||||
modifier = Modifier.size(17.dp),
|
||||
tint = ClawTheme.colors.text,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">كلمة المرور</string>
|
||||
<string name="run_onboarding_again">تشغيل الإعداد الأولي مرة أخرى</string>
|
||||
<string name="resolved_endpoint">نقطة النهاية التي تم حلها</string>
|
||||
<string name="gateway_setup">إعداد البوابة</string>
|
||||
<string name="connect_to_gateway">الاتصال ببوابتك</string>
|
||||
<string name="scan_setup_code">مسح رمز الإعداد</string>
|
||||
<string name="use_gateway_qr">استخدم رمز QR الخاص ببوابتك أو رمز الإعداد</string>
|
||||
<string name="nearby_gateway">بوابة قريبة</string>
|
||||
<string name="enter_gateway_url">أدخل عنوان URL للبوابة</string>
|
||||
<string name="connect_manual_url">الاتصال باستخدام عنوان URL يدوي</string>
|
||||
<string name="permissions">الأذونات</string>
|
||||
<string name="done">تم</string>
|
||||
<string name="gateway_trust_first_seen">تحقق من بصمة الشهادة قبل الوثوق بهذه البوابة.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">تم تغيير شهادة البوابة. تابع فقط إذا كنت تتوقع هذا التغيير.\n\nSHA-256 القديم:\n%1$s\n\nSHA-256 الجديد:\n%2$s</string>
|
||||
<string name="gateway_recovery">استرداد البوابة</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Passwort</string>
|
||||
<string name="run_onboarding_again">Onboarding erneut ausführen</string>
|
||||
<string name="resolved_endpoint">Aufgelöster Endpunkt</string>
|
||||
<string name="gateway_setup">Gateway-Einrichtung</string>
|
||||
<string name="connect_to_gateway">Mit Ihrem Gateway verbinden</string>
|
||||
<string name="scan_setup_code">Einrichtungscode scannen</string>
|
||||
<string name="use_gateway_qr">Verwenden Sie Ihren Gateway-QR- oder Einrichtungscode</string>
|
||||
<string name="nearby_gateway">Gateway in der Nähe</string>
|
||||
<string name="enter_gateway_url">Gateway-URL eingeben</string>
|
||||
<string name="connect_manual_url">Über eine manuelle URL verbinden</string>
|
||||
<string name="permissions">Berechtigungen</string>
|
||||
<string name="done">Fertig</string>
|
||||
<string name="gateway_trust_first_seen">Überprüfen Sie den Zertifikatfingerabdruck, bevor Sie diesem Gateway vertrauen.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Das Gateway-Zertifikat wurde geändert. Fahren Sie nur fort, wenn Sie diese Änderung erwartet haben.\n\nAlter SHA-256-Wert:\n%1$s\n\nNeuer SHA-256-Wert:\n%2$s</string>
|
||||
<string name="gateway_recovery">Gateway-Wiederherstellung</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Contraseña</string>
|
||||
<string name="run_onboarding_again">Ejecutar la incorporación de nuevo</string>
|
||||
<string name="resolved_endpoint">Endpoint resuelto</string>
|
||||
<string name="gateway_setup">Configuración de Gateway</string>
|
||||
<string name="connect_to_gateway">Conéctate a tu Gateway</string>
|
||||
<string name="scan_setup_code">Escanear código de configuración</string>
|
||||
<string name="use_gateway_qr">Usa el QR o código de configuración de tu Gateway</string>
|
||||
<string name="nearby_gateway">Gateway cercano</string>
|
||||
<string name="enter_gateway_url">Introduce la URL del gateway</string>
|
||||
<string name="connect_manual_url">Conectar usando una URL manual</string>
|
||||
<string name="permissions">Permisos</string>
|
||||
<string name="done">Listo</string>
|
||||
<string name="gateway_trust_first_seen">Verifica la huella digital del certificado antes de confiar en este gateway.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">El certificado del gateway cambió. Continúa solo si esperabas este cambio.\n\nSHA-256 anterior:\n%1$s\n\nSHA-256 nuevo:\n%2$s</string>
|
||||
<string name="gateway_recovery">Recuperación del gateway</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">رمز عبور</string>
|
||||
<string name="run_onboarding_again">اجرای دوباره فرایند شروع به کار</string>
|
||||
<string name="resolved_endpoint">نقطه پایانی حلشده</string>
|
||||
<string name="gateway_setup">راهاندازی دروازه</string>
|
||||
<string name="connect_to_gateway">به دروازه خود متصل شوید</string>
|
||||
<string name="scan_setup_code">اسکن کد راهاندازی</string>
|
||||
<string name="use_gateway_qr">از QR دروازه یا کد راهاندازی خود استفاده کنید</string>
|
||||
<string name="nearby_gateway">دروازه نزدیک</string>
|
||||
<string name="enter_gateway_url">URL دروازه را وارد کنید</string>
|
||||
<string name="connect_manual_url">اتصال با استفاده از URL دستی</string>
|
||||
<string name="permissions">مجوزها</string>
|
||||
<string name="done">انجام شد</string>
|
||||
<string name="gateway_trust_first_seen">پیش از اعتماد به این دروازه، اثر انگشت گواهی را تأیید کنید.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">گواهی دروازه تغییر کرده است. فقط در صورتی ادامه دهید که انتظار این تغییر را داشتید.\n\nSHA-256 قدیمی:\n%1$s\n\nSHA-256 جدید:\n%2$s</string>
|
||||
<string name="gateway_recovery">بازیابی دروازه</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Mot de passe</string>
|
||||
<string name="run_onboarding_again">Relancer l’intégration</string>
|
||||
<string name="resolved_endpoint">Point de terminaison résolu</string>
|
||||
<string name="gateway_setup">Configuration de la passerelle</string>
|
||||
<string name="connect_to_gateway">Connectez-vous à votre Gateway</string>
|
||||
<string name="scan_setup_code">Scanner le code de configuration</string>
|
||||
<string name="use_gateway_qr">Utilisez le QR de votre Gateway ou le code de configuration</string>
|
||||
<string name="nearby_gateway">Passerelle à proximité</string>
|
||||
<string name="enter_gateway_url">Saisir l’URL de la passerelle</string>
|
||||
<string name="connect_manual_url">Se connecter avec une URL manuelle</string>
|
||||
<string name="permissions">Autorisations</string>
|
||||
<string name="done">Terminé</string>
|
||||
<string name="gateway_trust_first_seen">Vérifiez l’empreinte du certificat avant d’accorder votre confiance à cette passerelle.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Le certificat de la passerelle a changé. Continuez uniquement si vous vous attendiez à ce changement.\n\nAncien SHA-256 :\n%1$s\n\nNouveau SHA-256 :\n%2$s</string>
|
||||
<string name="gateway_recovery">Récupération de la passerelle</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">पासवर्ड</string>
|
||||
<string name="run_onboarding_again">ऑनबोर्डिंग फिर से चलाएँ</string>
|
||||
<string name="resolved_endpoint">रिज़ॉल्व किया गया एंडपॉइंट</string>
|
||||
<string name="gateway_setup">गेटवे सेटअप</string>
|
||||
<string name="connect_to_gateway">अपने गेटवे से कनेक्ट करें</string>
|
||||
<string name="scan_setup_code">सेटअप कोड स्कैन करें</string>
|
||||
<string name="use_gateway_qr">अपने गेटवे QR या सेटअप कोड का उपयोग करें</string>
|
||||
<string name="nearby_gateway">नज़दीकी गेटवे</string>
|
||||
<string name="enter_gateway_url">गेटवे URL दर्ज करें</string>
|
||||
<string name="connect_manual_url">मैन्युअल URL का उपयोग करके कनेक्ट करें</string>
|
||||
<string name="permissions">अनुमतियाँ</string>
|
||||
<string name="done">हो गया</string>
|
||||
<string name="gateway_trust_first_seen">इस गेटवे पर भरोसा करने से पहले प्रमाणपत्र फ़िंगरप्रिंट सत्यापित करें।\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">गेटवे प्रमाणपत्र बदल गया है। केवल तभी जारी रखें जब आपको इस बदलाव की अपेक्षा थी।\n\nपुराना SHA-256:\n%1$s\n\nनया SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">गेटवे पुनर्प्राप्ति</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Kata sandi</string>
|
||||
<string name="run_onboarding_again">Jalankan onboarding lagi</string>
|
||||
<string name="resolved_endpoint">Endpoint yang diselesaikan</string>
|
||||
<string name="gateway_setup">Penyiapan Gateway</string>
|
||||
<string name="connect_to_gateway">Hubungkan ke Gateway Anda</string>
|
||||
<string name="scan_setup_code">Pindai kode penyiapan</string>
|
||||
<string name="use_gateway_qr">Gunakan QR Gateway atau kode penyiapan Anda</string>
|
||||
<string name="nearby_gateway">Gateway terdekat</string>
|
||||
<string name="enter_gateway_url">Masukkan URL gateway</string>
|
||||
<string name="connect_manual_url">Hubungkan menggunakan URL manual</string>
|
||||
<string name="permissions">Izin</string>
|
||||
<string name="done">Selesai</string>
|
||||
<string name="gateway_trust_first_seen">Verifikasi sidik jari sertifikat sebelum memercayai gateway ini.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Sertifikat gateway berubah. Lanjutkan hanya jika Anda mengharapkan perubahan ini.\n\nSHA-256 lama:\n%1$s\n\nSHA-256 baru:\n%2$s</string>
|
||||
<string name="gateway_recovery">Pemulihan gateway</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Password</string>
|
||||
<string name="run_onboarding_again">Esegui di nuovo l’onboarding</string>
|
||||
<string name="resolved_endpoint">Endpoint risolto</string>
|
||||
<string name="gateway_setup">Configurazione gateway</string>
|
||||
<string name="connect_to_gateway">Connettiti al tuo Gateway</string>
|
||||
<string name="scan_setup_code">Scansiona codice di configurazione</string>
|
||||
<string name="use_gateway_qr">Usa il QR del tuo Gateway o il codice di configurazione</string>
|
||||
<string name="nearby_gateway">Gateway nelle vicinanze</string>
|
||||
<string name="enter_gateway_url">Inserisci URL del gateway</string>
|
||||
<string name="connect_manual_url">Connetti usando un URL manuale</string>
|
||||
<string name="permissions">Autorizzazioni</string>
|
||||
<string name="done">Fine</string>
|
||||
<string name="gateway_trust_first_seen">Verifica l’impronta digitale del certificato prima di considerare attendibile questo gateway.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Il certificato del gateway è cambiato. Continua solo se ti aspettavi questa modifica.\n\nSHA-256 precedente:\n%1$s\n\nNuovo SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">Ripristino del gateway</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">パスワード</string>
|
||||
<string name="run_onboarding_again">オンボーディングを再実行</string>
|
||||
<string name="resolved_endpoint">解決済みエンドポイント</string>
|
||||
<string name="gateway_setup">ゲートウェイ設定</string>
|
||||
<string name="connect_to_gateway">ゲートウェイに接続</string>
|
||||
<string name="scan_setup_code">セットアップコードをスキャン</string>
|
||||
<string name="use_gateway_qr">ゲートウェイの QR またはセットアップコードを使用</string>
|
||||
<string name="nearby_gateway">近くのゲートウェイ</string>
|
||||
<string name="enter_gateway_url">ゲートウェイ URL を入力</string>
|
||||
<string name="connect_manual_url">手動 URL で接続</string>
|
||||
<string name="permissions">権限</string>
|
||||
<string name="done">完了</string>
|
||||
<string name="gateway_trust_first_seen">このゲートウェイを信頼する前に、証明書のフィンガープリントを確認してください。\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">ゲートウェイの証明書が変更されました。想定した変更である場合のみ続行してください。\n\n以前の SHA-256:\n%1$s\n\n新しい SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">ゲートウェイの復旧</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">비밀번호</string>
|
||||
<string name="run_onboarding_again">온보딩 다시 실행</string>
|
||||
<string name="resolved_endpoint">확인된 엔드포인트</string>
|
||||
<string name="gateway_setup">게이트웨이 설정</string>
|
||||
<string name="connect_to_gateway">게이트웨이에 연결</string>
|
||||
<string name="scan_setup_code">설정 코드 스캔</string>
|
||||
<string name="use_gateway_qr">게이트웨이 QR 또는 설정 코드 사용</string>
|
||||
<string name="nearby_gateway">주변 게이트웨이</string>
|
||||
<string name="enter_gateway_url">게이트웨이 URL 입력</string>
|
||||
<string name="connect_manual_url">수동 URL을 사용하여 연결</string>
|
||||
<string name="permissions">권한</string>
|
||||
<string name="done">완료</string>
|
||||
<string name="gateway_trust_first_seen">이 게이트웨이를 신뢰하기 전에 인증서 지문을 확인하세요.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">게이트웨이 인증서가 변경되었습니다. 예상한 변경인 경우에만 계속하세요.\n\n이전 SHA-256:\n%1$s\n\n새 SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">게이트웨이 복구</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Wachtwoord</string>
|
||||
<string name="run_onboarding_again">Onboarding opnieuw uitvoeren</string>
|
||||
<string name="resolved_endpoint">Opgelost endpoint</string>
|
||||
<string name="gateway_setup">Gateway instellen</string>
|
||||
<string name="connect_to_gateway">Verbinden met je Gateway</string>
|
||||
<string name="scan_setup_code">Setupcode scannen</string>
|
||||
<string name="use_gateway_qr">Gebruik je Gateway-QR-code of setupcode</string>
|
||||
<string name="nearby_gateway">Gateway in de buurt</string>
|
||||
<string name="enter_gateway_url">Gateway-URL invoeren</string>
|
||||
<string name="connect_manual_url">Verbinden met een handmatige URL</string>
|
||||
<string name="permissions">Machtigingen</string>
|
||||
<string name="done">Gereed</string>
|
||||
<string name="gateway_trust_first_seen">Controleer de certificaatvingerafdruk voordat je deze gateway vertrouwt.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Het gatewaycertificaat is gewijzigd. Ga alleen door als je deze wijziging verwachtte.\n\nOude SHA-256:\n%1$s\n\nNieuwe SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">Gatewayherstel</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Hasło</string>
|
||||
<string name="run_onboarding_again">Uruchom ponownie wdrażanie</string>
|
||||
<string name="resolved_endpoint">Rozpoznany punkt końcowy</string>
|
||||
<string name="gateway_setup">Konfiguracja bramy</string>
|
||||
<string name="connect_to_gateway">Połącz ze swoją bramą</string>
|
||||
<string name="scan_setup_code">Zeskanuj kod konfiguracji</string>
|
||||
<string name="use_gateway_qr">Użyj kodu QR bramy lub kodu konfiguracji</string>
|
||||
<string name="nearby_gateway">Pobliska brama</string>
|
||||
<string name="enter_gateway_url">Wprowadź URL bramy</string>
|
||||
<string name="connect_manual_url">Połącz, używając ręcznego URL</string>
|
||||
<string name="permissions">Uprawnienia</string>
|
||||
<string name="done">Gotowe</string>
|
||||
<string name="gateway_trust_first_seen">Sprawdź odcisk certyfikatu, zanim zaufasz tej bramie.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Certyfikat bramy uległ zmianie. Kontynuuj tylko wtedy, gdy oczekujesz tej zmiany.\n\nStary SHA-256:\n%1$s\n\nNowy SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">Odzyskiwanie bramy</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Senha</string>
|
||||
<string name="run_onboarding_again">Executar integração novamente</string>
|
||||
<string name="resolved_endpoint">Endpoint resolvido</string>
|
||||
<string name="gateway_setup">Configuração do Gateway</string>
|
||||
<string name="connect_to_gateway">Conecte-se ao seu Gateway</string>
|
||||
<string name="scan_setup_code">Escanear código de configuração</string>
|
||||
<string name="use_gateway_qr">Use o QR do seu Gateway ou o código de configuração</string>
|
||||
<string name="nearby_gateway">Gateway próximo</string>
|
||||
<string name="enter_gateway_url">Inserir URL do gateway</string>
|
||||
<string name="connect_manual_url">Conectar usando uma URL manual</string>
|
||||
<string name="permissions">Permissões</string>
|
||||
<string name="done">Concluído</string>
|
||||
<string name="gateway_trust_first_seen">Verifique a impressão digital do certificado antes de confiar neste gateway.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">O certificado do gateway foi alterado. Continue somente se você esperava essa alteração.\n\nSHA-256 antigo:\n%1$s\n\nSHA-256 novo:\n%2$s</string>
|
||||
<string name="gateway_recovery">Recuperação do gateway</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Пароль</string>
|
||||
<string name="run_onboarding_again">Запустить настройку заново</string>
|
||||
<string name="resolved_endpoint">Разрешенная конечная точка</string>
|
||||
<string name="gateway_setup">Настройка шлюза</string>
|
||||
<string name="connect_to_gateway">Подключитесь к своему шлюзу</string>
|
||||
<string name="scan_setup_code">Сканировать код настройки</string>
|
||||
<string name="use_gateway_qr">Используйте QR-код или код настройки вашего шлюза</string>
|
||||
<string name="nearby_gateway">Шлюз поблизости</string>
|
||||
<string name="enter_gateway_url">Введите URL шлюза</string>
|
||||
<string name="connect_manual_url">Подключиться с помощью URL вручную</string>
|
||||
<string name="permissions">Разрешения</string>
|
||||
<string name="done">Готово</string>
|
||||
<string name="gateway_trust_first_seen">Проверьте отпечаток сертификата, прежде чем доверять этому шлюзу.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Сертификат шлюза изменился. Продолжайте, только если вы ожидали это изменение.\n\nСтарый SHA-256:\n%1$s\n\nНовый SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">Восстановление шлюза</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Lösenord</string>
|
||||
<string name="run_onboarding_again">Kör introduktionen igen</string>
|
||||
<string name="resolved_endpoint">Löst slutpunkt</string>
|
||||
<string name="gateway_setup">Gateway-konfiguration</string>
|
||||
<string name="connect_to_gateway">Anslut till din gateway</string>
|
||||
<string name="scan_setup_code">Skanna konfigurationskod</string>
|
||||
<string name="use_gateway_qr">Använd gatewayens QR-kod eller konfigurationskod</string>
|
||||
<string name="nearby_gateway">Gateway i närheten</string>
|
||||
<string name="enter_gateway_url">Ange gateway-URL</string>
|
||||
<string name="connect_manual_url">Anslut med en manuell URL</string>
|
||||
<string name="permissions">Behörigheter</string>
|
||||
<string name="done">Klar</string>
|
||||
<string name="gateway_trust_first_seen">Verifiera certifikatets fingeravtryck innan du litar på denna gateway.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Gateway-certifikatet har ändrats. Fortsätt bara om du förväntade dig denna ändring.\n\nTidigare SHA-256:\n%1$s\n\nNy SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">Gateway-återställning</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">รหัสผ่าน</string>
|
||||
<string name="run_onboarding_again">เรียกใช้การเริ่มต้นใช้งานอีกครั้ง</string>
|
||||
<string name="resolved_endpoint">เอนด์พอยต์ที่แก้ไขแล้ว</string>
|
||||
<string name="gateway_setup">การตั้งค่าเกตเวย์</string>
|
||||
<string name="connect_to_gateway">เชื่อมต่อกับเกตเวย์ของคุณ</string>
|
||||
<string name="scan_setup_code">สแกนรหัสตั้งค่า</string>
|
||||
<string name="use_gateway_qr">ใช้ QR ของเกตเวย์หรือรหัสตั้งค่าของคุณ</string>
|
||||
<string name="nearby_gateway">เกตเวย์ใกล้เคียง</string>
|
||||
<string name="enter_gateway_url">ป้อน URL เกตเวย์</string>
|
||||
<string name="connect_manual_url">เชื่อมต่อโดยใช้ URL ด้วยตนเอง</string>
|
||||
<string name="permissions">สิทธิ์</string>
|
||||
<string name="done">เสร็จสิ้น</string>
|
||||
<string name="gateway_trust_first_seen">ตรวจสอบลายนิ้วมือของใบรับรองก่อนเชื่อถือเกตเวย์นี้\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">ใบรับรองของเกตเวย์มีการเปลี่ยนแปลง ดำเนินการต่อเมื่อคุณคาดว่าจะมีการเปลี่ยนแปลงนี้เท่านั้น\n\nSHA-256 เดิม:\n%1$s\n\nSHA-256 ใหม่:\n%2$s</string>
|
||||
<string name="gateway_recovery">การกู้คืนเกตเวย์</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Parola</string>
|
||||
<string name="run_onboarding_again">Başlangıç sürecini tekrar çalıştır</string>
|
||||
<string name="resolved_endpoint">Çözümlenen uç nokta</string>
|
||||
<string name="gateway_setup">Ağ Geçidi Kurulumu</string>
|
||||
<string name="connect_to_gateway">Ağ Geçidinize Bağlanın</string>
|
||||
<string name="scan_setup_code">Kurulum kodunu tara</string>
|
||||
<string name="use_gateway_qr">Gateway QR kodunuzu veya kurulum kodunuzu kullanın</string>
|
||||
<string name="nearby_gateway">Yakındaki ağ geçidi</string>
|
||||
<string name="enter_gateway_url">Ağ geçidi URL’sini girin</string>
|
||||
<string name="connect_manual_url">Manuel URL kullanarak bağlan</string>
|
||||
<string name="permissions">İzinler</string>
|
||||
<string name="done">Bitti</string>
|
||||
<string name="gateway_trust_first_seen">Bu ağ geçidine güvenmeden önce sertifika parmak izini doğrulayın.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Ağ geçidi sertifikası değişti. Yalnızca bu değişikliği bekliyorsanız devam edin.\n\nEski SHA-256:\n%1$s\n\nYeni SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">Ağ geçidi kurtarma</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Пароль</string>
|
||||
<string name="run_onboarding_again">Запустити адаптацію знову</string>
|
||||
<string name="resolved_endpoint">Визначена кінцева точка</string>
|
||||
<string name="gateway_setup">Налаштування шлюзу</string>
|
||||
<string name="connect_to_gateway">Підключіться до свого шлюзу</string>
|
||||
<string name="scan_setup_code">Сканувати код налаштування</string>
|
||||
<string name="use_gateway_qr">Використайте QR-код свого шлюзу або код налаштування</string>
|
||||
<string name="nearby_gateway">Шлюз поблизу</string>
|
||||
<string name="enter_gateway_url">Введіть URL-адресу шлюзу</string>
|
||||
<string name="connect_manual_url">Підключитися за допомогою URL-адреси вручну</string>
|
||||
<string name="permissions">Дозволи</string>
|
||||
<string name="done">Готово</string>
|
||||
<string name="gateway_trust_first_seen">Перевірте відбиток сертифіката, перш ніж довіряти цьому шлюзу.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Сертифікат шлюзу змінився. Продовжуйте, лише якщо ви очікували цю зміну.\n\nСтарий SHA-256:\n%1$s\n\nНовий SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">Відновлення шлюзу</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Mật khẩu</string>
|
||||
<string name="run_onboarding_again">Chạy hướng dẫn thiết lập lại</string>
|
||||
<string name="resolved_endpoint">Điểm cuối đã phân giải</string>
|
||||
<string name="gateway_setup">Thiết lập cổng</string>
|
||||
<string name="connect_to_gateway">Kết nối với Gateway của bạn</string>
|
||||
<string name="scan_setup_code">Quét mã thiết lập</string>
|
||||
<string name="use_gateway_qr">Sử dụng mã QR Gateway hoặc mã thiết lập của bạn</string>
|
||||
<string name="nearby_gateway">Cổng gần đây</string>
|
||||
<string name="enter_gateway_url">Nhập URL cổng</string>
|
||||
<string name="connect_manual_url">Kết nối bằng URL thủ công</string>
|
||||
<string name="permissions">Quyền</string>
|
||||
<string name="done">Xong</string>
|
||||
<string name="gateway_trust_first_seen">Xác minh dấu vân tay chứng chỉ trước khi tin cậy cổng này.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">Chứng chỉ cổng đã thay đổi. Chỉ tiếp tục nếu bạn mong đợi thay đổi này.\n\nSHA-256 cũ:\n%1$s\n\nSHA-256 mới:\n%2$s</string>
|
||||
<string name="gateway_recovery">Khôi phục cổng</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">密码</string>
|
||||
<string name="run_onboarding_again">再次运行引导流程</string>
|
||||
<string name="resolved_endpoint">已解析的端点</string>
|
||||
<string name="gateway_setup">网关设置</string>
|
||||
<string name="connect_to_gateway">连接到你的网关</string>
|
||||
<string name="scan_setup_code">扫描设置代码</string>
|
||||
<string name="use_gateway_qr">使用你的网关 QR 码或设置代码</string>
|
||||
<string name="nearby_gateway">附近的网关</string>
|
||||
<string name="enter_gateway_url">输入网关 URL</string>
|
||||
<string name="connect_manual_url">使用手动 URL 连接</string>
|
||||
<string name="permissions">权限</string>
|
||||
<string name="done">完成</string>
|
||||
<string name="gateway_trust_first_seen">验证证书指纹后再信任此网关。\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">网关证书已更改。仅当这是您预期的更改时才继续。\n\n旧 SHA-256:\n%1$s\n\n新 SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">网关恢复</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">密碼</string>
|
||||
<string name="run_onboarding_again">再次執行新手導覽</string>
|
||||
<string name="resolved_endpoint">已解析的端點</string>
|
||||
<string name="gateway_setup">閘道設定</string>
|
||||
<string name="connect_to_gateway">連接到您的閘道</string>
|
||||
<string name="scan_setup_code">掃描設定碼</string>
|
||||
<string name="use_gateway_qr">使用您的 Gateway QR 或設定碼</string>
|
||||
<string name="nearby_gateway">附近的閘道</string>
|
||||
<string name="enter_gateway_url">輸入閘道 URL</string>
|
||||
<string name="connect_manual_url">使用手動 URL 連接</string>
|
||||
<string name="permissions">權限</string>
|
||||
<string name="done">完成</string>
|
||||
<string name="gateway_trust_first_seen">請先驗證憑證指紋,再信任此閘道。\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">閘道憑證已變更。僅在這是您預期的變更時繼續。\n\n舊 SHA-256:\n%1$s\n\n新 SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">閘道復原</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
<string name="password">Password</string>
|
||||
<string name="run_onboarding_again">Run onboarding again</string>
|
||||
<string name="resolved_endpoint">Resolved endpoint</string>
|
||||
<string name="gateway_setup">Gateway Setup</string>
|
||||
<string name="connect_to_gateway">Connect to your Gateway</string>
|
||||
<string name="scan_setup_code">Scan setup code</string>
|
||||
<string name="use_gateway_qr">Use your Gateway QR or setup code</string>
|
||||
<string name="nearby_gateway">Nearby gateway</string>
|
||||
<string name="enter_gateway_url">Enter gateway URL</string>
|
||||
<string name="connect_manual_url">Connect using a manual URL</string>
|
||||
<string name="permissions">Permissions</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="gateway_trust_first_seen">Verify the certificate fingerprint before trusting this gateway.\n\n%1$s</string>
|
||||
<string name="gateway_trust_changed">The gateway certificate changed. Continue only if you expected this.\n\nOld SHA-256:\n%1$s\n\nNew SHA-256:\n%2$s</string>
|
||||
<string name="gateway_recovery">Gateway Recovery</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import ai.openclaw.app.gateway.GatewayEndpoint
|
|||
import ai.openclaw.app.gateway.GatewaySession
|
||||
import ai.openclaw.app.gateway.GatewayTlsProbeFailure
|
||||
import ai.openclaw.app.gateway.GatewayTlsProbeResult
|
||||
import ai.openclaw.app.node.ConnectionManager
|
||||
import ai.openclaw.app.node.InvokeDispatcher
|
||||
import ai.openclaw.app.protocol.OpenClawTalkCommand
|
||||
import ai.openclaw.app.voice.TalkModeManager
|
||||
|
|
@ -212,6 +213,74 @@ class GatewayBootstrapAuthTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun operatorConnectScopesForAuthUsesNativeScopesWhenNoStoredOperatorMetadata() {
|
||||
assertEquals(
|
||||
ConnectionManager.nativeClientOperatorScopes,
|
||||
operatorConnectScopesForAuth(
|
||||
usesStoredDeviceToken = false,
|
||||
storedOperatorScopes = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun operatorConnectScopesForAuthPreservesStoredScopesForReconnects() {
|
||||
val storedScopes = listOf("operator.approvals", "operator.read", "operator.write")
|
||||
|
||||
assertEquals(
|
||||
storedScopes,
|
||||
operatorConnectScopesForAuth(
|
||||
usesStoredDeviceToken = true,
|
||||
storedOperatorScopes = storedScopes,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun operatorConnectScopesForAuthFallsBackToLegacyScopesForOldStoredDeviceTokens() {
|
||||
assertEquals(
|
||||
ConnectionManager.legacyOperatorScopes,
|
||||
operatorConnectScopesForAuth(
|
||||
usesStoredDeviceToken = true,
|
||||
storedOperatorScopes = emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun operatorConnectScopesForAuthUsesNativeScopesForExplicitReauth() {
|
||||
assertEquals(
|
||||
ConnectionManager.nativeClientOperatorScopes,
|
||||
operatorConnectScopesForAuth(
|
||||
usesStoredDeviceToken = false,
|
||||
storedOperatorScopes = listOf("operator.approvals", "operator.read", "operator.write"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun operatorSessionUsesStoredDeviceTokenOnlyWithoutExplicitSharedAuth() {
|
||||
assertTrue(
|
||||
operatorSessionUsesStoredDeviceToken(
|
||||
auth = NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = "bootstrap-1", password = null),
|
||||
storedOperatorToken = "stored-token",
|
||||
),
|
||||
)
|
||||
assertFalse(
|
||||
operatorSessionUsesStoredDeviceToken(
|
||||
auth = NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
|
||||
storedOperatorToken = "stored-token",
|
||||
),
|
||||
)
|
||||
assertFalse(
|
||||
operatorSessionUsesStoredDeviceToken(
|
||||
auth = NodeRuntime.GatewayConnectAuth(token = null, bootstrapToken = null, password = "password"),
|
||||
storedOperatorToken = "stored-token",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nodeConnectStartsOperatorAfterBootstrapHandoffWhenOperatorWasConnecting() {
|
||||
val app = RuntimeEnvironment.getApplication()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package ai.openclaw.app
|
||||
|
||||
import ai.openclaw.app.gateway.GatewayConnectErrorDetails
|
||||
import ai.openclaw.app.gateway.GatewaySession
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
|
|
@ -8,33 +11,6 @@ import org.junit.Assert.assertTrue
|
|||
import org.junit.Test
|
||||
|
||||
class GatewayNodeApprovalStateTest {
|
||||
@Test
|
||||
fun exactApprovalCommandsAgeOutToStatusFallbacks() {
|
||||
assertEquals(
|
||||
GatewayNodeCapabilityApproval.PendingApproval(requestId = null),
|
||||
GatewayNodeCapabilityApproval.PendingApproval(requestId = "request-1").withoutExactRequestId(),
|
||||
)
|
||||
assertEquals(
|
||||
GatewayNodeCapabilityApproval.PendingReapproval(requestId = null),
|
||||
GatewayNodeCapabilityApproval.PendingReapproval(requestId = "request-2").withoutExactRequestId(),
|
||||
)
|
||||
assertNull(GatewayNodeCapabilityApproval.PendingApproval(requestId = null).withoutExactRequestId())
|
||||
|
||||
val summary =
|
||||
GatewayNodesDevicesSummary(
|
||||
nodes = listOf(pendingNode(requestId = "request-1")),
|
||||
pendingDevices = emptyList(),
|
||||
pairedDevices = emptyList(),
|
||||
)
|
||||
assertNull(
|
||||
summary
|
||||
.withoutExactApprovalRequestIds()
|
||||
.nodes
|
||||
.single()
|
||||
.pendingRequestId,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesGatewayNodeApprovalState() {
|
||||
assertEquals(GatewayNodeApprovalState.Approved, parseGatewayNodeApprovalState("approved"))
|
||||
|
|
@ -45,6 +21,48 @@ class GatewayNodeApprovalStateTest {
|
|||
assertEquals(GatewayNodeApprovalState.Loading, parseGatewayNodeApprovalState("future-state"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nodePairingEventsInvalidateNodeDeviceState() {
|
||||
assertTrue(gatewayEventInvalidatesNodesDevices("node.pair.requested"))
|
||||
assertTrue(gatewayEventInvalidatesNodesDevices("node.pair.resolved"))
|
||||
assertFalse(gatewayEventInvalidatesNodesDevices("device.pair.resolved"))
|
||||
assertFalse(gatewayEventInvalidatesNodesDevices("exec.approval.resolved"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nodePairingFailuresRefreshNodeDeviceState() {
|
||||
assertTrue(
|
||||
nodeConnectFailureNeedsApprovalRefresh(
|
||||
GatewaySession.ErrorShape(
|
||||
code = "NOT_PAIRED",
|
||||
message = "pairing required",
|
||||
details =
|
||||
GatewayConnectErrorDetails(
|
||||
code = "PAIRING_REQUIRED",
|
||||
canRetryWithDeviceToken = false,
|
||||
recommendedNextStep = "wait_then_retry",
|
||||
pauseReconnect = false,
|
||||
reason = "not-paired",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
assertFalse(
|
||||
nodeConnectFailureNeedsApprovalRefresh(
|
||||
GatewaySession.ErrorShape(
|
||||
code = "UNAUTHORIZED",
|
||||
message = "token mismatch",
|
||||
details =
|
||||
GatewayConnectErrorDetails(
|
||||
code = "AUTH_TOKEN_MISMATCH",
|
||||
canRetryWithDeviceToken = false,
|
||||
recommendedNextStep = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesNodeListApprovalFields() {
|
||||
val node =
|
||||
|
|
@ -71,6 +89,45 @@ class GatewayNodeApprovalStateTest {
|
|||
assertEquals(listOf("device.status"), node.commands)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesSplitNodeListShapeFromGateway() {
|
||||
val root =
|
||||
Json
|
||||
.parseToJsonElement(
|
||||
"""
|
||||
{
|
||||
"pending": [
|
||||
{
|
||||
"nodeId": "pending-node",
|
||||
"paired": false,
|
||||
"connected": false,
|
||||
"approvalState": "pending-approval",
|
||||
"pendingRequestId": "request-pending"
|
||||
}
|
||||
],
|
||||
"paired": [
|
||||
{
|
||||
"nodeId": "self",
|
||||
"paired": true,
|
||||
"connected": true,
|
||||
"approvalState": "approved",
|
||||
"caps": ["device"],
|
||||
"commands": ["device.status"]
|
||||
}
|
||||
]
|
||||
}
|
||||
""".trimIndent(),
|
||||
).jsonObject
|
||||
|
||||
val nodes = parseGatewayNodeList(root)
|
||||
|
||||
assertEquals(2, nodes.size)
|
||||
assertEquals(
|
||||
GatewayNodeCapabilityApproval.Approved,
|
||||
currentNodeCapabilityApproval(nodes = nodes, selfNodeId = "self"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun treatsMissingNodeApprovalStateAsUnsupported() {
|
||||
val node =
|
||||
|
|
@ -113,14 +170,14 @@ class GatewayNodeApprovalStateTest {
|
|||
paired = true,
|
||||
connected = true,
|
||||
approvalState = GatewayNodeApprovalState.PendingApproval,
|
||||
pendingRequestId = null,
|
||||
pendingRequestId = "request-self",
|
||||
capabilities = emptyList(),
|
||||
commands = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
GatewayNodeCapabilityApproval.PendingApproval(requestId = null),
|
||||
GatewayNodeCapabilityApproval.PendingApproval("request-self"),
|
||||
currentNodeCapabilityApproval(nodes = nodes, selfNodeId = "self"),
|
||||
)
|
||||
assertEquals(
|
||||
|
|
@ -129,47 +186,17 @@ class GatewayNodeApprovalStateTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentPhoneApprovalCarriesOnlySafePendingRequestIds() {
|
||||
val safe = pendingNode(requestId = "request-1")
|
||||
val unsafe = pendingNode(requestId = "request-1;echo unsafe")
|
||||
|
||||
assertEquals(
|
||||
GatewayNodeCapabilityApproval.PendingApproval("request-1"),
|
||||
currentNodeCapabilityApproval(nodes = listOf(safe), selfNodeId = "self"),
|
||||
)
|
||||
assertEquals(
|
||||
GatewayNodeCapabilityApproval.PendingApproval(requestId = null),
|
||||
currentNodeCapabilityApproval(nodes = listOf(unsafe), selfNodeId = "self"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ignoresStaleNodeApprovalRefreshResults() {
|
||||
val guard = GatewayNodeApprovalRefreshGuard()
|
||||
var approval: GatewayNodeCapabilityApproval = GatewayNodeCapabilityApproval.Loading
|
||||
var approvalState = GatewayNodeApprovalState.Loading
|
||||
val staleRefresh = guard.begin()
|
||||
val currentRefresh = guard.begin()
|
||||
|
||||
assertFalse(guard.publishIfCurrent(staleRefresh) { approval = GatewayNodeCapabilityApproval.Approved })
|
||||
assertFalse(guard.publishIfCurrent(staleRefresh) { approvalState = GatewayNodeApprovalState.Approved })
|
||||
assertTrue(
|
||||
guard.publishIfCurrent(currentRefresh) { approval = GatewayNodeCapabilityApproval.PendingReapproval("request-2") },
|
||||
guard.publishIfCurrent(currentRefresh) { approvalState = GatewayNodeApprovalState.PendingReapproval },
|
||||
)
|
||||
assertEquals(GatewayNodeCapabilityApproval.PendingReapproval("request-2"), approval)
|
||||
assertEquals(GatewayNodeApprovalState.PendingReapproval, approvalState)
|
||||
}
|
||||
|
||||
private fun pendingNode(requestId: String): GatewayNodeSummary =
|
||||
GatewayNodeSummary(
|
||||
id = "self",
|
||||
displayName = null,
|
||||
remoteIp = null,
|
||||
version = null,
|
||||
deviceFamily = null,
|
||||
paired = true,
|
||||
connected = true,
|
||||
approvalState = GatewayNodeApprovalState.PendingApproval,
|
||||
pendingRequestId = requestId,
|
||||
capabilities = emptyList(),
|
||||
commands = emptyList(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,37 @@ class SecurePrefsTest {
|
|||
assertTrue(plainPrefs.getBoolean("device.apps.sharing.enabled", false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cameraSharing_defaultsOffAndPersistsOptIn() {
|
||||
val context = RuntimeEnvironment.getApplication()
|
||||
val plainPrefs = context.getSharedPreferences("openclaw.node", Context.MODE_PRIVATE)
|
||||
plainPrefs.edit().clear().commit()
|
||||
val prefs = SecurePrefs(context)
|
||||
|
||||
assertFalse(prefs.cameraEnabled.value)
|
||||
assertFalse(plainPrefs.getBoolean("camera.enabled", true))
|
||||
|
||||
prefs.setCameraEnabled(true)
|
||||
|
||||
assertTrue(prefs.cameraEnabled.value)
|
||||
assertTrue(plainPrefs.getBoolean("camera.enabled", false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cameraSharing_migratesExistingInstallsToPreviousDefault() {
|
||||
val context = RuntimeEnvironment.getApplication()
|
||||
val plainPrefs = context.getSharedPreferences("openclaw.node", Context.MODE_PRIVATE)
|
||||
plainPrefs
|
||||
.edit()
|
||||
.clear()
|
||||
.putString("node.instanceId", "existing-node")
|
||||
.commit()
|
||||
val prefs = SecurePrefs(context)
|
||||
|
||||
assertTrue(prefs.cameraEnabled.value)
|
||||
assertTrue(plainPrefs.getBoolean("camera.enabled", false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun appearanceThemeMode_defaultsDarkForExistingInstalls() {
|
||||
val context = RuntimeEnvironment.getApplication()
|
||||
|
|
|
|||
|
|
@ -423,6 +423,7 @@ class GatewaySessionInvokeTest {
|
|||
"operator.approvals",
|
||||
"operator.pairing",
|
||||
"operator.read",
|
||||
"operator.talk.secrets",
|
||||
"operator.write",
|
||||
),
|
||||
)
|
||||
|
|
@ -441,6 +442,7 @@ class GatewaySessionInvokeTest {
|
|||
listOf(
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.talk.secrets",
|
||||
"operator.write",
|
||||
),
|
||||
params.scopes(),
|
||||
|
|
@ -602,7 +604,10 @@ class GatewaySessionInvokeTest {
|
|||
assertEquals("bootstrap-node-token", nodeEntry?.token)
|
||||
assertEquals(emptyList<String>(), nodeEntry?.scopes)
|
||||
assertEquals("bootstrap-operator-token", operatorEntry?.token)
|
||||
assertEquals(listOf("operator.approvals", "operator.read", "operator.write"), operatorEntry?.scopes)
|
||||
assertEquals(
|
||||
listOf("operator.approvals", "operator.read", "operator.talk.secrets", "operator.write"),
|
||||
operatorEntry?.scopes,
|
||||
)
|
||||
} finally {
|
||||
shutdownHarness(harness, server)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -427,19 +427,33 @@ class ConnectionManagerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun buildOperatorConnectOptions_requestsQrBootstrapHandoffScopes() {
|
||||
fun buildOperatorConnectOptions_requestsNativeClientOperatorScopes() {
|
||||
val options = newManager().buildOperatorConnectOptions()
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.write",
|
||||
),
|
||||
ConnectionManager.nativeClientOperatorScopes,
|
||||
options.scopes,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun operatorScopesForStoredDeviceToken_preservesRecordedScopes() {
|
||||
assertEquals(
|
||||
listOf("operator.read", "operator.write"),
|
||||
ConnectionManager.operatorScopesForStoredDeviceToken(
|
||||
listOf("operator.read", "operator.write", "operator.read", " "),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun operatorScopesForStoredDeviceToken_fallsBackToLegacyScopesWhenMetadataMissing() {
|
||||
assertEquals(
|
||||
ConnectionManager.legacyOperatorScopes,
|
||||
ConnectionManager.operatorScopesForStoredDeviceToken(emptyList()),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun buildNodeConnectOptions_advertisesRequestableSmsSearchWithoutSmsCapability() {
|
||||
val options =
|
||||
|
|
|
|||
|
|
@ -248,6 +248,17 @@ class GatewayConfigResolverTest {
|
|||
assertNull(resolved.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveScannedSetupCodeResultAcceptsEmulatorSetupCode() {
|
||||
val setupCode =
|
||||
encodeSetupCode("""{"url":"ws://10.0.2.2:18789","bootstrapToken":"bootstrap-1"}""")
|
||||
|
||||
val resolved = resolveScannedSetupCodeResult(setupCode)
|
||||
|
||||
assertEquals(setupCode, resolved.setupCode)
|
||||
assertNull(resolved.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveScannedSetupCodeResultAcceptsQrJsonPayload() {
|
||||
val setupCode =
|
||||
|
|
@ -404,6 +415,18 @@ class GatewayConfigResolverTest {
|
|||
assertNull(decoded?.password)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun manualTokenDetectsSetupCodePayloads() {
|
||||
val setupCode =
|
||||
encodeSetupCode("""{"url":"ws://10.0.2.2:18789","bootstrapToken":"bootstrap-1"}""")
|
||||
val qrPayload = """{"setupCode":"$setupCode"}"""
|
||||
|
||||
assertEquals(true, manualTokenLooksLikeSetupCode(setupCode))
|
||||
assertEquals(true, manualTokenLooksLikeSetupCode(qrPayload))
|
||||
assertEquals(false, manualTokenLooksLikeSetupCode("local-mobile-test"))
|
||||
assertEquals(false, manualTokenLooksLikeSetupCode(""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveGatewayConnectConfigPrefersBootstrapTokenFromSetupCode() {
|
||||
val setupCode =
|
||||
|
|
@ -431,6 +454,32 @@ class GatewayConfigResolverTest {
|
|||
assertEquals("", resolved?.password)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveGatewayConnectConfigAcceptsQrJsonSetupCodePayload() {
|
||||
val setupCode =
|
||||
encodeSetupCode("""{"url":"wss://gateway.example:18789","bootstrapToken":"bootstrap-1"}""")
|
||||
val qrPayload = """{"setupCode":"$setupCode"}"""
|
||||
|
||||
val resolved =
|
||||
resolveGatewayConnectConfig(
|
||||
useSetupCode = true,
|
||||
setupCode = qrPayload,
|
||||
manualHostInput = "",
|
||||
manualPortInput = "",
|
||||
manualTlsInput = false,
|
||||
bootstrapTokenInput = "",
|
||||
tokenInput = "shared-token",
|
||||
passwordInput = "shared-password",
|
||||
)
|
||||
|
||||
assertEquals("gateway.example", resolved?.host)
|
||||
assertEquals(18789, resolved?.port)
|
||||
assertEquals(true, resolved?.tls)
|
||||
assertEquals("bootstrap-1", resolved?.bootstrapToken)
|
||||
assertEquals("", resolved?.token)
|
||||
assertEquals("", resolved?.password)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveGatewayConnectConfigDefaultsPortlessWssSetupCodeTo443() {
|
||||
val setupCode =
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -22,7 +22,7 @@ ktlint-gradle = "14.2.0"
|
|||
kotlin = "2.4.0"
|
||||
material = "1.14.0"
|
||||
okhttp = "5.3.2"
|
||||
play-services-code-scanner = "16.1.0"
|
||||
barcode-scanning = "17.3.0"
|
||||
robolectric = "4.16.1"
|
||||
serialization-json = "1.11.0"
|
||||
|
||||
|
|
@ -32,6 +32,7 @@ androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro
|
|||
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
|
||||
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
|
||||
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
||||
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
||||
androidx-camera-video = { module = "androidx.camera:camera-video", version.ref = "androidx-camera" }
|
||||
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "androidx-compose-bom" }
|
||||
androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
|
||||
|
|
@ -47,6 +48,7 @@ androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "a
|
|||
androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "androidx-uiautomator" }
|
||||
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "androidx-webkit" }
|
||||
bcprov = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bcprov" }
|
||||
barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcode-scanning" }
|
||||
commonmark = { module = "org.commonmark:commonmark", version.ref = "commonmark" }
|
||||
commonmark-ext-autolink = { module = "org.commonmark:commonmark-ext-autolink", version.ref = "commonmark" }
|
||||
commonmark-ext-gfm-strikethrough = { module = "org.commonmark:commonmark-ext-gfm-strikethrough", version.ref = "commonmark" }
|
||||
|
|
@ -63,7 +65,6 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa
|
|||
material = { module = "com.google.android.material:material", version.ref = "material" }
|
||||
mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||
play-services-code-scanner = { module = "com.google.android.gms:play-services-code-scanner", version.ref = "play-services-code-scanner" }
|
||||
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
|
||||
|
||||
[plugins]
|
||||
|
|
|
|||
|
|
@ -127,9 +127,9 @@ That bootstrap token carries the built-in pairing bootstrap profile:
|
|||
`node` plus a bounded `operator` handoff
|
||||
- the handed-off `node` token stays `scopes: []`
|
||||
- the handed-off `operator` token is limited to `operator.approvals`,
|
||||
`operator.read`, and `operator.write`
|
||||
- `operator.admin` and `operator.pairing` are not granted by QR/setup-code
|
||||
bootstrap; they require a separate approved operator pairing or token flow
|
||||
`operator.read`, `operator.talk.secrets`, and `operator.write`
|
||||
- `operator.admin` is not granted by QR/setup-code bootstrap; it requires a
|
||||
separate approved operator pairing or token flow
|
||||
- later token rotation/revocation remains bounded by both the device's approved
|
||||
role contract and the caller session's operator scopes
|
||||
|
||||
|
|
|
|||
|
|
@ -495,7 +495,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
|||
- `/pair approve` when there is only one pending request
|
||||
- `/pair approve latest` for most recent
|
||||
|
||||
The setup code carries a short-lived bootstrap token. Built-in setup-code bootstrap is node-only: the first connect creates a pending node request, and after approval the Gateway returns a durable node token with `scopes: []`. It does not return a handed-off operator token; operator access requires a separate approved operator pairing or token flow.
|
||||
The setup code carries a short-lived bootstrap token. Built-in setup-code bootstrap returns a durable node token with `scopes: []` plus a bounded operator handoff token for trusted mobile onboarding. That operator token can read setup-time native configuration, but it does not grant pairing mutation scopes or `operator.admin`.
|
||||
|
||||
If a device retries with changed auth details (for example role/scopes/public key), the previous pending request is superseded and the new request uses a different `requestId`. Re-run `/pair pending` before approving.
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ openclaw qr --url wss://gateway.example/ws
|
|||
- `--token` and `--password` are mutually exclusive.
|
||||
- The setup code itself now carries an opaque short-lived `bootstrapToken`, not the shared gateway token/password.
|
||||
- Built-in setup-code bootstrap returns a primary `node` token with `scopes: []` plus a bounded `operator` handoff token for trusted mobile onboarding.
|
||||
- The handed-off operator token is limited to `operator.approvals`, `operator.read`, `operator.talk.secrets`, and `operator.write`; `operator.admin` and `operator.pairing` require a separate approved operator pairing or token flow.
|
||||
- The handed-off operator token is limited to `operator.approvals`, `operator.read`, `operator.talk.secrets`, and `operator.write`; pairing mutation scopes and `operator.admin` still require a separate approved operator pairing or token flow.
|
||||
- Mobile pairing fails closed for Tailscale/public `ws://` gateway URLs. Private LAN addresses and `.local` Bonjour hosts remain supported over `ws://`, but Tailscale/public mobile routes should use Tailscale Serve/Funnel or a `wss://` gateway URL.
|
||||
- With `--remote`, OpenClaw requires either `gateway.remote.url` or
|
||||
`gateway.tailscale.mode=serve|funnel`.
|
||||
|
|
|
|||
|
|
@ -169,10 +169,11 @@ operator token:
|
|||
```
|
||||
|
||||
The operator handoff is intentionally bounded so QR onboarding can start the
|
||||
mobile operator loop without granting `operator.admin` or `operator.pairing`.
|
||||
It does include `operator.talk.secrets` so the native client can read the Talk
|
||||
configuration it needs after bootstrap. Broader admin and pairing scopes require
|
||||
a separate approved operator pairing or token flow. Clients should persist
|
||||
mobile operator loop and complete native setup without granting pairing
|
||||
mutation scopes or `operator.admin`. It includes `operator.talk.secrets` so the
|
||||
native client can read the Talk configuration it needs after bootstrap. Broader
|
||||
pairing and admin access requires a separate approved operator pairing or token
|
||||
flow. Clients should persist
|
||||
`hello-ok.auth.deviceTokens` only
|
||||
when the connect used bootstrap auth on trusted transport such as `wss://` or
|
||||
loopback/local pairing.
|
||||
|
|
@ -731,8 +732,8 @@ rather than the pre-handshake defaults.
|
|||
- Built-in setup-code bootstrap returns the primary node
|
||||
`hello-ok.auth.deviceToken` plus a bounded operator token in
|
||||
`hello-ok.auth.deviceTokens` for trusted mobile handoff. The operator token
|
||||
includes `operator.talk.secrets` for native Talk configuration reads and
|
||||
excludes `operator.admin` and `operator.pairing`.
|
||||
includes `operator.talk.secrets` for native Talk configuration reads, but
|
||||
excludes pairing mutation scopes and `operator.admin`.
|
||||
- While a non-baseline setup-code bootstrap is waiting for approval, `PAIRING_REQUIRED`
|
||||
details include `recommendedNextStep: "wait_then_retry"`, `retryable: true`,
|
||||
and `pauseReconnect: false`. Clients should keep reconnecting with the same
|
||||
|
|
|
|||
|
|
@ -1531,7 +1531,7 @@ lives on the [Models FAQ](/help/faq-models).
|
|||
- On `AUTH_TOKEN_MISMATCH`, trusted clients can attempt one bounded retry with a cached device token when the gateway returns retry hints (`canRetryWithDeviceToken=true`, `recommendedNextStep=retry_with_device_token`).
|
||||
- That cached-token retry now reuses the cached approved scopes stored with the device token. Explicit `deviceToken` / explicit `scopes` callers still keep their requested scope set instead of inheriting cached scopes.
|
||||
- Outside that retry path, connect auth precedence is explicit shared token/password first, then explicit `deviceToken`, then stored device token, then bootstrap token.
|
||||
- Built-in setup-code bootstrap is node-only. After approval, it returns a node device token with `scopes: []` and does not return a handed-off operator token.
|
||||
- Built-in setup-code bootstrap returns a node device token with `scopes: []` plus a bounded operator handoff token for trusted mobile onboarding. The operator handoff can read setup-time native configuration but does not grant pairing mutation scopes or `operator.admin`.
|
||||
|
||||
Fix:
|
||||
|
||||
|
|
|
|||
|
|
@ -1201,7 +1201,6 @@ export function registerControlUiAndPairingSuite(): void {
|
|||
"operator.write",
|
||||
]);
|
||||
expect(operatorHandoff?.scopes).not.toContain("operator.admin");
|
||||
expect(operatorHandoff?.scopes).not.toContain("operator.pairing");
|
||||
|
||||
const pendingAfterInitial = await listDevicePairing();
|
||||
const pendingForDevice = pendingAfterInitial.pending.filter(
|
||||
|
|
@ -1370,7 +1369,6 @@ export function registerControlUiAndPairingSuite(): void {
|
|||
"operator.write",
|
||||
]);
|
||||
expect(operatorHandoff?.scopes).not.toContain("operator.admin");
|
||||
expect(operatorHandoff?.scopes).not.toContain("operator.pairing");
|
||||
|
||||
const pendingAfterInitial = await listDevicePairing();
|
||||
expect(
|
||||
|
|
@ -1510,7 +1508,6 @@ export function registerControlUiAndPairingSuite(): void {
|
|||
"operator.write",
|
||||
]);
|
||||
expect(operatorHandoff?.scopes).not.toContain("operator.admin");
|
||||
expect(operatorHandoff?.scopes).not.toContain("operator.pairing");
|
||||
wsRetry.close();
|
||||
|
||||
await expect(
|
||||
|
|
|
|||
|
|
@ -1398,8 +1398,7 @@ export function attachGatewayWsMessageHandler(params: GatewayWsMessageHandlerPar
|
|||
// This is the native QR/setup-code onboarding seam. Mobile clients
|
||||
// must prove their canonical client id and platform/family metadata
|
||||
// agree before the Gateway can skip owner approval and hand off the
|
||||
// bounded operator token below. Admin/pairing scopes still require
|
||||
// an explicit owner flow.
|
||||
// bounded operator token below. Admin/pairing still require an explicit owner flow.
|
||||
const bootstrapPairingRoles = allowSetupCodeMobileBootstrapPairing
|
||||
? uniqueStrings([role, ...boundBootstrapProfile.roles])
|
||||
: undefined;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ describe("device bootstrap profile", () => {
|
|||
"node.exec",
|
||||
"operator.admin",
|
||||
"operator.approvals",
|
||||
"operator.pairing",
|
||||
"operator.read",
|
||||
"operator.talk.secrets",
|
||||
"operator.write",
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ describe("native app i18n inventory", () => {
|
|||
(entry) => entry.source === "Talk failed: Realtime provider closed unexpectedly.",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(entries.some((entry) => entry.source === "Scan fresh setup code")).toBe(true);
|
||||
expect(entries.some((entry) => entry.source === "Retry connection")).toBe(true);
|
||||
expect(entries.some((entry) => entry.source === "Scan QR code")).toBe(true);
|
||||
expect(entries.some((entry) => entry.source === "Test connection")).toBe(true);
|
||||
expect(entries.some((entry) => entry.source === "Searching…")).toBe(true);
|
||||
expect(entries.some((entry) => entry.source === "Run now")).toBe(true);
|
||||
expect(entries.some((entry) => entry.source === "Loading chat")).toBe(true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue