Pair a wall panel through the portal
A screen somewhere this installation is not reachable from asks the portal for a code instead, and the portal mints its credential — because a token signed here is one such a device could never present. Where it was minted changes nothing about what it may do. The panel gate moved off the branch that decodes a local panel token and onto whatever claims name a panel, so the portal's and this installation's are bounded by the same check against the same panel's dashboards. A token of that scope naming no panel is refused rather than left holding the account it borrows. The connector marks what arrives on its socket, since that is the only thing that makes it true, and the approval screen now names what is holding a code — approving adopts whatever answers, so it is worth a look first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017F9RnYCJgASuBTcAjxmnsp
This commit is contained in:
@@ -2,8 +2,8 @@ import { useMutation, useQuery } from "@tanstack/react-query"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { useEffect } from "react"
|
||||
|
||||
import { PanelsService } from "@/client"
|
||||
import { appPath } from "@/lib/portal"
|
||||
import { ApiError, PanelsService } from "@/client"
|
||||
import { appPath, isPortal, openPortalSession } from "@/lib/portal"
|
||||
|
||||
/**
|
||||
* Adopting a screen that has no keyboard.
|
||||
@@ -15,6 +15,11 @@ import { appPath } from "@/lib/portal"
|
||||
*
|
||||
* Outside both shells like `/view/{name}`: until it is paired, this device has
|
||||
* no session and there is nothing to put around it.
|
||||
*
|
||||
* Reached through the portal as well, for a screen hanging where this
|
||||
* installation is not: the hub serves this one page without a session and
|
||||
* forwards these two calls down the tunnel, because a device with no
|
||||
* credential is what they are for.
|
||||
*/
|
||||
export const Route = createFileRoute("/panel/")({
|
||||
component: PairPanel,
|
||||
@@ -24,7 +29,13 @@ export const Route = createFileRoute("/panel/")({
|
||||
function PairPanel() {
|
||||
// A code lives ten minutes. Asking for one is the mount, and asking again is
|
||||
// what happens when this one is no longer recognised.
|
||||
const start = useMutation({ mutationFn: () => PanelsService.startPairing() })
|
||||
const start = useMutation({
|
||||
mutationFn: () => PanelsService.startPairing(),
|
||||
// A screen hung while the link is down has nobody to tell, so it keeps
|
||||
// asking rather than showing six dots until someone power-cycles it.
|
||||
retry: true,
|
||||
retryDelay: 5000,
|
||||
})
|
||||
const request = start.mutate
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: asked for once, when the screen goes up.
|
||||
@@ -47,18 +58,32 @@ function PairPanel() {
|
||||
})
|
||||
|
||||
// Expired, or collected already. Ask for another rather than leaving a
|
||||
// number on the wall that no longer works.
|
||||
// number on the wall that no longer works — but only for that answer: over
|
||||
// the tunnel a 502 or 503 is an ordinary hiccup, and changing the code on
|
||||
// the wall while somebody is typing it is worse than waiting.
|
||||
useEffect(() => {
|
||||
if (error) request()
|
||||
if (error instanceof ApiError && error.status === 404) request()
|
||||
}, [error, request])
|
||||
|
||||
useEffect(() => {
|
||||
if (!status?.access_token || !status.panel) return
|
||||
localStorage.setItem("access_token", status.access_token)
|
||||
// A full load rather than a route change: everything this page asked for
|
||||
// was asked without a credential, and the socket has to dial again holding
|
||||
// this one.
|
||||
window.location.href = appPath(`/panel/${status.panel}`)
|
||||
const token = status.access_token
|
||||
const panel = status.panel
|
||||
const land = () => {
|
||||
// A full load rather than a route change: everything this page asked for
|
||||
// was asked without a credential, and the socket has to dial again
|
||||
// holding this one.
|
||||
window.location.href = appPath(`/panel/${panel}`)
|
||||
}
|
||||
if (isPortal()) {
|
||||
// Under the portal the credential belongs in the hub's own cookie: the
|
||||
// page is served with its config injected, and localStorage is not read
|
||||
// there. Landing anyway if it fails would loop on a page with nothing.
|
||||
openPortalSession(token).then(land).catch(land)
|
||||
return
|
||||
}
|
||||
localStorage.setItem("access_token", token)
|
||||
land()
|
||||
}, [status])
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user