Files
HIPCTF2/docs/guides/notifications.md
T

5.7 KiB

type, title, description, tags, timestamp
type title description tags timestamp
guide Notifications SPA-wide notification store + the global HTTP error interceptor that translates backend error codes and statuses into friendly user-facing messages.
guide
notifications
error
interceptor
toast
ux
2026-07-23T00:10:00Z

Components

Symbol Path Responsibility
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 catches HttpErrorResponse, looks up the error code, and pushes a friendly message into NotificationService.
NotificationService.messages signal (consumed by future toast UI) Currently the messages signal is appended in-memory only; the list itself doubles as the integration point for a future toast/banner component. Each push also mirrors the message to the browser console (console.error for error, console.info for info).

Wiring

frontend/src/main.ts registers the interceptor after authInterceptor so the auth header is already attached when the error interceptor sees the request:

provideHttpClient(
  withInterceptors([csrfInterceptor, authInterceptor, errorNotificationInterceptor]),
)

The interceptor:

  1. Catches every HttpErrorResponse thrown from any HTTP call.
  2. Maps the response to a friendly message using friendlyMessage(status, err.error).
  3. Suppresses notifications for endpoints that have their own user-facing error presentation (see Suppression rules below).
  4. Pushes the message via NotificationService.error(...).
  5. Re-throws the original error so the caller still receives the ApiErrorEnvelope (e.g. ChallengesApiService.submit reads envelope.code to render the modal's inline error).

Friendly message mapping

friendlyMessage(status, body) resolves in this order:

Source Message
status === 0 (network / CORS / aborted) Network error. Please try again.
body.code matches FRIENDLY_MESSAGES The mapped friendly text (see table below).
status === 401 or 403 Your session is no longer valid. (unless suppressed).
status === 404 Not found.
status >= 500 Server error. Please try again later.
body.message (non-empty) Falls back to that message.
otherwise Request failed (<status>).

FRIENDLY_MESSAGES lookup table:

Error code Friendly text
EVENT_NOT_RUNNING Submissions are only accepted while the event is running.
FLAG_INCORRECT Incorrect flag.
UNAUTHORIZED Your session is no longer valid.
FORBIDDEN You are not allowed to perform this action.
NOT_FOUND Not found.
VALIDATION_FAILED Invalid input.

Suppression rules

Some errors are expected and have their own UI. To avoid double-toasting:

URL pattern Status Why
/api/v1/auth/login /csrf /register 401 A failed login or first-fetch of the CSRF cookie produces a 401; the form already renders the inline error.
/api/v1/challenges/status 401 The challenges page uses the snapshot as a bootstrap hint only — the SSE stream (/api/v1/events) is the source of truth and will deliver the next frame.

In both cases the interceptor still re-throws the error so callers can react locally; only the notification push is skipped.

See also

  • Challenges Board (uses errorNotificationInterceptor for non-flag submission failures)
  • Authenticated Shell (cross-tab invalidation propagates from a 401 SSE response via NotificationService is not triggered — the shell uses its own peer-invalidation channel)
  • REST API Overview (error envelope)