Files
HIPCTF2/docs/architecture/frontend-structure.md
T
2026-07-23 03:47:06 +00:00

9.7 KiB

type, title, description, tags, timestamp
type title description tags timestamp
architecture Frontend Structure Angular routes, components, services, guards, interceptors, and authenticated SSE transport.
architecture
frontend
angular
shell
sse
challenges
notifications
2026-07-23T00:10:00Z

Routes

Routes live in frontend/src/app/app.routes.ts:

Path Component Guard Notes
/bootstrap SetupCreateAdminComponent First-admin creation flow.
/login LandingComponent landingGuard Public landing and authentication modal.
/ HomeComponent authGuard Authenticated shell and child outlet.
/challenges ChallengesPage inherited Full challenges board (category columns, modal, flag submit, live SSE).
/scoreboard ScoreboardPage inherited Placeholder scoreboard page.
/blog BlogPage inherited Placeholder blog page.
/admin AdminUsersComponent adminGuard Admin-only child route.
/admin/challenges AdminChallengesComponent inherited Sortable/searchable admin Challenges list, Add/Edit modal, import/export.

Components and services

Symbol Path Responsibility
HomeComponent frontend/src/app/features/home/home.component.ts Smart shell; owns navigation signals, user hydration, change-password flow, event-stream lifecycle, peer-invalidation subscription, and per-session resets of both UserStore and ChallengesStore (the latter flushes the per-user board cache on logout / SSE unauthorized / peer-logout).
ShellHeaderComponent frontend/src/app/features/shell/header/shell-header.component.ts Renders title, active section, event LED/countdown, and user menu; contains component-scoped LED styling.
QuickTabsComponent frontend/src/app/features/shell/tabs/quick-tabs.component.ts Emits navigation events for Challenges, Scoreboard, and Blog.
EventStatusStore frontend/src/app/core/services/event-status.store.ts Applies event state frames and computes countdown text; re-exports the pure LED color mapping; wires an optional onUnauthorized callback for the new SSE 401/403 event.
AuthenticatedEventSourceService frontend/src/app/core/services/authenticated-event-source.service.ts Opens authenticated SSE over fetch, parsing streamed frames into EventSourceLike events; dispatches a separate 'unauthorized' event for 401/403.
AuthService frontend/src/app/core/services/auth.service.ts Stores the access token used by the authenticated SSE transport; owns the cross-tab invalidation BroadcastChannel + storage-event fallback and exposes clearAndBroadcast / onPeerInvalidation.
UserStore frontend/src/app/core/services/user.store.ts Hydrates the signed-in user and rank data.
auth-session-events.pure.ts frontend/src/app/core/services/auth-session-events.pure.ts Pure cross-tab message encoding/validation and the namespaced channel / storage-key constants.
ChallengesStore frontend/src/app/features/challenges/challenges.store.ts Signal store backing /challenges: board payload, event state, per-card solve listeners, SSE solve-frame mutation, submit response application, public reset() for the session-boundary flush, and setMyUserId(id) (now returns the previous id and resets whenever the cached state belongs to an unknown or different user).
ChallengesApiService frontend/src/app/features/challenges/challenges.service.ts HTTP wrapper around /api/v1/challenges/{board,status,:id,:id/solves} returning Observable<...> and translating HttpErrorResponse into a typed ApiErrorEnvelope.
challenges.pure.ts frontend/src/app/features/challenges/challenges.pure.ts Pure types + helpers for the challenges feature (BoardCard, SolverRow, SolveEventPayload, sorting, parsers, friendly error mapping, formatDdHhMm).
ChallengeCardComponent / CategoryColumnComponent / ChallengeModalComponent frontend/src/app/features/challenges/ Presentational components for the board grid, column, and detail modal.
NotificationService frontend/src/app/core/services/notification.service.ts Root-provided signal-backed store of { id, kind, message, ts } records; error() / info() push, dismiss(id) / clear() remove.
errorNotificationInterceptor frontend/src/app/core/interceptors/error-notification.interceptor.ts Functional HTTP interceptor that pushes a friendly message into NotificationService for every HttpErrorResponse; suppresses duplicate toasts for endpoints with their own error UI (login form, /challenges/status snapshot).

Authenticated SSE wiring

HomeComponent.ngOnInit() calls EventStatusStore.start() with a factory for AuthenticatedEventSourceService.open('/api/v1/events/status'). The transport:

  1. Reads the access token from AuthService.
  2. Sends Accept: text/event-stream, optional Authorization: Bearer <token>, credentials: 'include', and an AbortSignal.
  3. Reads the response body with a stream reader and decodes UTF-8 chunks.
  4. Splits complete SSE records on blank lines, joins repeated data: lines, and dispatches MessageEvent objects.
  5. Buffers records received before the message listener is attached.
  6. Inspects the response status before the ok/body check; on 401 or 403 dispatches a separate 'unauthorized' event and returns without reading the body. Generic transport failures still dispatch 'error'.
  7. Aborts and clears listeners when close() is called.

EventStatusStore.start(createSource, onUnauthorized?) parses each message as EventStatePayload, updates its signals, runs a one-second local timer, and — when onUnauthorized is provided — invokes it on 'unauthorized' events. HomeComponent passes an onUnauthorized callback that delegates to its shared handleSessionInvalidated('sse-unauthorized') flow and stops the store on destruction.

Key integration files

File Responsibility
frontend/src/app/app.routes.ts Registers shell and child routes.
frontend/src/app/core/interceptors/auth.interceptor.ts Adds Bearer authentication to Angular HTTP requests.
frontend/src/app/core/interceptors/error-notification.interceptor.ts Translates HttpErrorResponse into friendly messages and pushes them to NotificationService (with suppression rules for login + /challenges/status).
frontend/src/app/core/services/authenticated-event-source.service.ts Adds Bearer authentication to the browser SSE request, which does not use the Angular HTTP interceptor chain.
frontend/src/app/core/services/event-status.pure.ts Defines the event payload and transport interface.
frontend/src/app/features/challenges/challenges.page.ts /challenges smart page; owns gate logic, modal lifecycle, SSE wiring, and the countdown-zero reload.
frontend/src/app/features/challenges/challenges.store.ts Signal store backing /challenges: board, event state, per-card solve listeners, SSE solve-frame mutation, submit response application, public reset(), and per-user setMyUserId flush.
tests/frontend/authenticated-event-source.spec.ts Regression coverage for authenticated SSE transport behavior.

Event-status pure helpers

frontend/src/app/core/services/event-status.pure.ts owns the small, no-Angular types and pure functions consumed by the event status pipeline. EventStatusStore re-exports them so application code can import from a single path.

Export Purpose
EventState Union 'running' | 'countdown' | 'stopped' | 'unconfigured'.
EventStatePayload Server-pushed event window payload shape.
EventSourceLike Minimal interface for any SSE transport the store can drive.
formatDdHhMm(seconds) Pure DD:HH:mm:ss formatter (zero-padded, falls back to 00:00:00:00 for negative / non-finite input).
deriveCountdownText(state, start, end) Pure mapping from event window state to countdown label.
LED_COLOR_BY_STATE Readonly<Record<EventState, string>> mapping each state to its theme CSS variable (var(--color-success), --color-warning, --color-danger, --color-secondary).
ledBackgroundColor(state) Pure accessor returning the background-color value for the LED element.

ShellHeaderComponent exposes a computed signal ledColor() that calls ledBackgroundColor(this.eventState()) and binds it to the LED via [style.background-color]. The component's .led base rule is therefore shape-only (size, radius, inline-block, vertical alignment); the visible color always comes from LED_COLOR_BY_STATE and is guaranteed to be a non-transparent theme token. The four led-<state> classes remain on the element as selectors/test hooks but no longer own the color.

Cross-tab session invalidation

AuthService (constructed once at root) installs a single BroadcastChannel('hipctf.auth.v1') and a localStorage storage-event fallback. The shape of the broadcast payload is defined in auth-session-events.pure.ts and contains only { type, origin, ts } — no tokens or user data. clearAndBroadcast() is invoked from logout() and from the SSE unauthorized callback; peer listeners clear their local state without rebroadcasting. HomeComponent subscribes via auth.onPeerInvalidation(...), resets UserStore, closes any open shell overlays, and navigates to /login. See Cross-Tab Authenticated Shell Invalidation for the full contract and tester matrix.

See also