Skip to content

An installed PWA has no reachable path to a fronting proxy's sign-in page #31

Description

@Optic00

I run Variant C behind a proxy that authenticates devices before anything reaches the bridge. Two
things combine so that a lost session leaves the installed PWA with nothing usable on screen and no
way back in. I hit this in my own deployment and had to build a workaround for it, which is really
the point of this report: the workaround is ugly in a way that suggests the shape upstream is
missing.

The service worker answers every navigation from the precache

registerRoute(new NavigationRoute(createHandlerBoundToURL("/index.html"), { denylist: [/^\/api\//] }));

Every navigation except one under /api/ is served from the precached app shell without touching
the network. Confirmed in the built bundle, not just the source: web/dist/sw.js on 0.14.2 contains
denylist:[/^\/api\//].

So a sign-in or enrolment page served by the proxy at / is invisible to an installed PWA. It loads
the cached Collie UI instead, and there is no address bar to fall back to.

The workaround this forces

My proxy serves its enrolment page at /api/__enrol. Not because that is a sensible place for a
page, but because the /api/ denylist is the only crack through the precache. To be reachable from
an installed PWA, an operator has to put a non-API HTML page inside the namespace the API owns, and
order it ahead of the rule that would otherwise answer /api/* with a 401.

That works, and it is squatting. If Collie ever routed /api/__* itself, or tightened the denylist,
it would break silently. A supported escape hatch would remove the guesswork: a documented path that
the service worker always passes to the network, so a fronting proxy has somewhere legitimate to put
a sign-in page.

The other half, and why it makes this worse

Until the operator can reach that page, they also cannot tell they need to. Every loader error
except AbortError lands in the same catch and returns the last-good data flagged stale, which the
connection banner renders as "reconnecting". A 401 from the proxy therefore looks exactly like a
dead radio. It is self-sustaining too: the escalation latch clears only via markLive(), so under a
persistent 401 it never clears.

That half is not proxy-specific. The bridge answers 403 from checkAccess and guard for
host not allowed, cross-origin rejected and device not authorised, so a misconfigured
COLLIE_PUBLIC_HOSTS or a device that is not on the allowlist presents the same way.

I have opened #30 for that half separately, since it seemed clear cut: classify 401 and 403 in the
loaders and let the banner say it is not a connection problem. ApiError already carries the status,
it was simply never read.

What I am asking for

Only the routing half, because it is your call. An extra denylist entry, a dedicated path the
service worker always passes through, or something else entirely. Changing navigation routing in an
installed PWA is easy to get wrong in a way that locks people out, so I would rather build whichever
shape you prefer than guess at it. Happy to do that once you say which.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions