Merge remote-tracking branch 'gitea-ai/fix/companion-autologin-replay-intro'
Demo images / Build & push demo images (push) Successful in 2m49s
Demo images / Build & push demo images (push) Successful in 2m49s
This commit is contained in:
@@ -161,6 +161,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="wgError" class="text-xs text-red-400 text-center mb-3">{{ wgError }}</p>
|
||||
<button
|
||||
v-if="wgError && !wgLoading"
|
||||
type="button"
|
||||
class="inline-flex w-full items-center justify-center rounded-lg bg-white/5 border border-white/15 px-4 py-2.5 text-sm font-medium text-white/80 hover:bg-white/10 hover:text-white transition-colors mb-3"
|
||||
@click="retryWgPeer"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
|
||||
<!-- Same-device path: a phone can't scan its own screen, so offer
|
||||
the config as a file WireGuard can import. -->
|
||||
@@ -318,7 +326,15 @@ const POST_INTRO_GRACE_MS = 2000
|
||||
|
||||
let calmTicker: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
// Running inside the companion app's own WebView (it injects this JS bridge).
|
||||
// The "get the companion app" pitch is nonsense there — the user is already in
|
||||
// it — and the WG steps mid-pairing race the phone's changing network (the
|
||||
// "Failed to fetch" dead-end QR). Server/tunnel management for connected
|
||||
// companions lives in the NESMenu instead.
|
||||
const IN_COMPANION_APP = typeof (window as { ArchipelagoNative?: unknown }).ArchipelagoNative !== 'undefined'
|
||||
|
||||
onMounted(() => {
|
||||
if (IN_COMPANION_APP) return
|
||||
try {
|
||||
if (localStorage.getItem(STORAGE_KEY) !== '1') {
|
||||
setTimeout(maybeShow, BASE_DELAY_MS)
|
||||
@@ -471,6 +487,19 @@ function backFromPair() {
|
||||
}
|
||||
}
|
||||
|
||||
// Network-class failures: the node itself was unreachable (as opposed to the
|
||||
// backend answering with an RPC error). Includes the client's own timeout.
|
||||
const WG_NETWORK_ERR = /failed to fetch|networkerror|load failed|abort|request timeout/i
|
||||
|
||||
// Auto-retry ladder for network-class failures. On a first install this step
|
||||
// is often reached while the backend is still settling (services starting,
|
||||
// backend restarting during container orchestration) — a single failed fetch
|
||||
// left a permanently blank QR unless the user spotted the retry button.
|
||||
const WG_RETRY_DELAYS_MS = [2000, 4000, 8000]
|
||||
|
||||
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))
|
||||
const stillOnWgStep = () => visible.value && step.value === 'wgqr'
|
||||
|
||||
// Create (or fetch) the phone's VPN peer and render its config as a QR.
|
||||
// Reuses the same RPCs as the Server page's Add Device modal; the peer is
|
||||
// looked up first so reopening the modal never duplicates it.
|
||||
@@ -478,30 +507,70 @@ async function loadWgPeer() {
|
||||
if (wgQrDataUrl.value || wgLoading.value) return
|
||||
wgLoading.value = true
|
||||
wgError.value = ''
|
||||
try {
|
||||
const listed = await rpcClient
|
||||
.call<{ peers: { name: string }[] }>({ method: 'vpn.list-peers' })
|
||||
.catch(() => ({ peers: [] as { name: string }[] }))
|
||||
const exists = (listed.peers || []).some((p) => p.name === WG_PEER_NAME)
|
||||
const res = await rpcClient.call<{ config: string; peer_ip: string }>({
|
||||
method: exists ? 'vpn.peer-config' : 'vpn.create-peer',
|
||||
params: { name: WG_PEER_NAME },
|
||||
})
|
||||
wgConfig.value = res.config
|
||||
wgQrDataUrl.value = await QRCode.toDataURL(res.config, {
|
||||
width: 512,
|
||||
margin: 3,
|
||||
errorCorrectionLevel: 'M',
|
||||
color: {
|
||||
dark: '#111111',
|
||||
light: '#ffffff',
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
wgError.value = e instanceof Error ? e.message : 'Failed to generate the tunnel config'
|
||||
} finally {
|
||||
wgLoading.value = false
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
try {
|
||||
await provisionWgPeer()
|
||||
break
|
||||
} catch (e) {
|
||||
const raw = e instanceof Error ? e.message : ''
|
||||
const isNetworkErr = WG_NETWORK_ERR.test(raw)
|
||||
if (isNetworkErr && attempt < WG_RETRY_DELAYS_MS.length && stillOnWgStep()) {
|
||||
await sleep(WG_RETRY_DELAYS_MS[attempt])
|
||||
if (stillOnWgStep()) continue
|
||||
}
|
||||
// fetch()'s raw "Failed to fetch" means the node itself was unreachable —
|
||||
// after the retry ladder that's usually the phone's network mid-change
|
||||
// (WiFi drop, or a half-configured tunnel already routing 10.44.0.0/16).
|
||||
// Say so, and leave a Retry path instead of a dead end.
|
||||
wgError.value = isNetworkErr
|
||||
? "Can't reach your node. Check the phone is on the same network as the node (and any half-set-up tunnel is switched off), then tap Try again."
|
||||
: raw || 'Failed to generate the tunnel config'
|
||||
break
|
||||
}
|
||||
}
|
||||
wgLoading.value = false
|
||||
}
|
||||
|
||||
async function provisionWgPeer() {
|
||||
const listed = await rpcClient
|
||||
.call<{ peers: { name: string }[] }>({ method: 'vpn.list-peers' })
|
||||
.catch(() => null)
|
||||
// list-peers unreachable → don't guess "doesn't exist": create-peer on an
|
||||
// existing name would fail. Try create first, fall back to peer-config.
|
||||
const exists = listed === null
|
||||
? null
|
||||
: (listed.peers || []).some((p) => p.name === WG_PEER_NAME)
|
||||
let res: { config: string; peer_ip: string }
|
||||
if (exists === true) {
|
||||
res = await rpcClient.call({ method: 'vpn.peer-config', params: { name: WG_PEER_NAME } })
|
||||
} else {
|
||||
try {
|
||||
res = await rpcClient.call({ method: 'vpn.create-peer', params: { name: WG_PEER_NAME } })
|
||||
} catch (e) {
|
||||
// Peer already provisioned on a previous visit (list failed or raced).
|
||||
const msg = e instanceof Error ? e.message : ''
|
||||
if (/exist|duplicate/i.test(msg)) {
|
||||
res = await rpcClient.call({ method: 'vpn.peer-config', params: { name: WG_PEER_NAME } })
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
wgConfig.value = res.config
|
||||
wgQrDataUrl.value = await QRCode.toDataURL(res.config, {
|
||||
width: 512,
|
||||
margin: 3,
|
||||
errorCorrectionLevel: 'M',
|
||||
color: {
|
||||
dark: '#111111',
|
||||
light: '#ffffff',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function retryWgPeer() {
|
||||
wgError.value = ''
|
||||
void loadWgPeer()
|
||||
}
|
||||
|
||||
// Same-device path: hand the config to the WireGuard app as an importable
|
||||
|
||||
Reference in New Issue
Block a user