Skip to content

Latest commit

 

History

History
133 lines (107 loc) · 7.11 KB

File metadata and controls

133 lines (107 loc) · 7.11 KB

nusisb-ahlab-org

Public web app for the NUS Internal Shuttle Bus dashboard — live shuttle ETAs, bus positions, a campus map, service alerts, and a diagnostics view. Served by GitHub Pages at https://nusisb.ahlab.org.

Static single-page PWA (no build step). All live data comes from the Apps Script backend nusisb-ahlab-appscript, which hides the ConnectX FMS token; this repo ships no secrets.

Responsive: desktop gets the multi-column departure board; phones (detected via body.is-mobile) get a phone-native layout — compact app bar, vertical cards, and a bottom tab bar — rendered by parallel *Mobile() functions that reuse the same data and helpers. All mobile styling is scoped under body.is-mobile, so the desktop layout is untouched.

Mobile Stops view extras (all body.is-mobile-gated):

  • Install banner — captures beforeinstallprompt and shows an "Install App" card; on iOS (no prompt API) shows Add-to-Home-Screen instructions. Hidden once installed (display-mode: standalone) or dismissed.
  • Nearest stopsnavigator.geolocation.watchPosition shows the two closest stops from stop-coords.json, each with an animated metres count and a compass arrow (rotated by bearing − deviceHeading, unwrapped so it never spins the long way past 0/360; tap the compass for the iOS DeviceOrientation permission). Within 5 m it shows "You're here" instead of a distance. Degrades gracefully if location is denied.
  • Stop picker — "Add a stop" opens a searchable bottom sheet (#stopPicker) listing every stop, marking already-tracked ones and capping at MAX_STOPS.

How it talks to the backend

index.html sets window.API_BASE to the backend's Apps Script /exec URL. The frontend rewrites /api/<Name>?<params><exec>?action=<Name>&<params>, so the same HTML also runs against the local Node dev server (sclab/nus-isb-web) when API_BASE is empty.

File Notes
index.html the whole app (views, routing, rendering, AHL sign-in gate)
sw.js service worker — network-first HTML, cache-first assets, skips cross-origin API
manifest.json + icon-* PWA install metadata
og-image.png + og-card.html 1200×630 social-share card and its HTML source
route-geometry.json, map-base.* pre-baked map + road-following route polylines
stop-coords.json frozen stop coordinates (stops don't move; snapshot from the dev server's live accumulation)
CNAME nusisb.ahlab.org

AHL sign-in gate

The dashboard is gated to @ahlab.org members using the shared AHL member-login broker (ahl-site-appscript) — the same pattern as new.ahlab.org, with no GCP OAuth client. On load an overlay (#authGate) covers the app; clicking Sign in navigates the whole tab to the broker, which reads the visitor's Workspace identity server-side (Session.getActiveUser() — trustworthy because the broker webapp is Workspace-gated) and redirects back to /auth-callback/ with the identity in the URL fragment. auth-callback/ verifies a single-use nonce, caches the identity in localStorage (7-day TTL, matching the broker token), and returns to /, where ahlGrant() reveals the dashboard (body.ahl-authed) and starts polling (startApp()). Returning visitors on the same browser skip the gate entirely until the TTL lapses. Visitors signed into a non-@ahlab.org Google account get the broker's account chooser (a "Choose your AHL account" button into Google's AccountChooser, which resumes this exact login), not a dead-end.

Note: a genuine first sign-in still shows the broker's one-tap Continue page before bouncing back — Apps Script sandboxes its HTML and only allows top-window navigation from a user gesture, so an automatic redirect isn't possible (Google's documented restriction). With the localStorage session this is seen at most once per 7 days per browser, not on every load.

The ahlAuth* / ahlLogin functions live at the bottom of index.html's main <script>; storage keys (ahl-auth-v1, …) match auth-callback/index.html.

This is a UI access gate, not a security boundary — the bus-data proxy (nusisb-ahlab-appscript) stays ANYONE_ANONYMOUS so the browser can fetch() it cross-origin. (A DOMAIN-access Apps Script can't be fetched from another origin; it returns Google's login HTML instead of JSON. Fully server-side gating like ahl-meeting-appscript only works because that app's HTML is served by Apps Script on the same origin — not the case for a static GitHub Pages site.)

One-time setup — broker allowlist: https://nusisb.ahlab.org/auth-callback/ must be listed in ALLOWED_RETURN_URLS in ahl-site-appscript/Code.js (already added); redeploy that broker (npm run deploy in ahl-site-appscript) for it to take effect. The broker /exec URL is hard-coded as AHL_BROKER_URL in index.html — update it there if the broker is ever redeployed to a new URL.

Social share preview (Open Graph)

<head> carries Open Graph + Twitter Card tags pointing at og-image.png. To regenerate the card after editing og-card.html:

"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --headless=new --hide-scrollbars --window-size=1200,630 \
  --screenshot="og-image.png" "file://$(pwd)/og-card.html"

After deploying, re-scrape the link (Facebook Sharing Debugger, or just resend in WhatsApp) to refresh the sticky preview cache.

Performance — first-load latency

Each Apps Script call costs ~1.3–3.6s (302 redirect + runtime + upstream FMS fetch), and same-user calls are concurrency-throttled, so the old "fire ~7 requests and await Promise.all" first load took ~6–10s. Fixes:

  • Single bootstrap call (refreshBootstrap): in production the app makes one request to nusisb-ahlab-appscript's ?action=bootstrap&stops=…, which returns shuttles + activeBus + busPositions + stopRoutes + announcements in one round-trip (the backend fans out to the FMS server-side). Warm ≈ 2s vs ~6–10s. The local Node dev server has no bootstrap endpoint, so dev falls back to per-endpoint calls (refreshIndividual).
  • Lazy TickerTapes: the ~106 KB alerts feed is fetched only when the Updates view is opened (ensureTickers), not on every load.
  • Skeletons: shuttle/buses show shimmer placeholders while the first bootstrap call is in flight, instead of "Loading…".
  • Keep-warm + cache TTLs (backend): ActiveBus cache 5s→15s; an optional 1-minute keepWarm trigger pre-warms the proxy caches. Enable once from the Apps Script editor: run setupKeepWarmTrigger() (trade-off: it polls the FMS every minute around the clock).

Deploy

Push to main; GitHub Pages serves the root. DNS: nusisb CNAME → augmented-human-lab.github.io.

If live data stops loading, open the Diagnostics tab — it shows the backend health, masked token, and a live FMS validity check. A rotated token is fixed in the backend (see nusisb-ahlab-appscript), not here.

Source / dev

Active development happens in sclab/nus-isb-web (Node dev server + mitmproxy token-capture tooling + docs). This repo is the deployable production frontend.