Skip to content

fix(approuter): enable websockets so /event-display WS upgrade isn't 403'd - #1694

Merged
jung-thomas merged 1 commit into
mainfrom
worktree-fix-event-display-websocket-403
Aug 12, 2026
Merged

jung-thomas merged 1 commit into
mainfrom
worktree-fix-event-display-websocket-403

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Symptom

/event-display/?eventId=...&theme=joule shows a WebSocket connection error. Browser console:

WebSocket connection to 'wss://<approuter-host>/socket.io/?EIO=4&transport=websocket'
failed: Error during WebSocket handshake: Unexpected response code: 403

Reproduced on the PROD approuter host directly (so not Akamai) — and identically on DEV.

Root cause

approuter/xs-app.json had no top-level websockets block. Stock @sap/approuter v16.9.0 rejects every WebSocket upgrade when websockets aren't enabled:

// approuter/node_modules/@sap/approuter/lib/websockets/WsProxy.js:84-85
if (!req.routerConfig.appConfig.websockets.enabled) {
  return authorizationRequired(req, cb, 'websockets are disabled'); // -> 403 Forbidden
}

This gate runs before the Origin check, so the upgrade never reaches the CAP backend. HTTP polling GETs to the same /socket.io/ route pass through (verified 200 + valid engine.io sid), which is why the initial handshake and the /rest/event-stream/getEventBuckets fetch worked but the live stream never connected.

The event-display client (hugo-apps/src/event-display/useEventStream.ts:83-88) opens Socket.IO with transports: ['websocket'] (no long-poll fallback), so the failed upgrade surfaces immediately as connect_error.

The custom upgrade proxy in approuter/server.js is isLocal-gated, so this only affected deployed envs (DEV/QA/PROD), never local cds watch — which masked it.

The @cap-js-community/websocket plugin docs call for exactly this ({ "websockets": { "enabled": true } }); it was simply never added here.

Fix

  • Add "websockets": { "enabled": true } to approuter/xs-app.json (validated against the approuter's own xs-app-schema.json — enabled: boolean required).
  • Bump MTA patch version 1.13.5 → 1.13.6.

Verification

  • Config validates against the approuter schema; JSON parses.
  • Live evidence pre-fix: polling /socket.io/ → 200 (+sid); WS upgrade → 403 Forbidden; browser console 403 during handshake.
  • Post-deploy check (a full approuter rebuild is required — this is an approuter route/config change, so mbt build + full deploy, no --skip-build/-m scoping): load /event-display/?eventId=10000004&theme=joule, confirm no connect_error, and that a WS upgrade to /socket.io/?...&transport=websocket returns 101, not 403.

Follow-up to consider (out of scope here)

When served from the vanity domain developers.sap.com (behind Akamai), the browser Origin is https://developers.sap.com. The approuter's post-enablement Origin check must see that as the app URL (via forwarded host) or it will 403 on origin instead. If that surfaces, set WS_ALLOWED_ORIGINS accordingly. Not needed for the cfapps approuter host in this report.

…403'd

The event-display kiosk (hugo-apps/src/event-display) opens a Socket.IO
connection with transports:['websocket'] (no long-poll fallback) to
namespace /ws/event-stream via the default /socket.io/ engine.io path.

On deployed envs the WS upgrade returned "403 Forbidden" during the
handshake. Root cause: xs-app.json had no top-level `websockets` block.
Stock @sap/approuter v16.9.0 rejects every upgrade when it isn't enabled
(lib/websockets/WsProxy.js:84-85 -> authorizationRequired 'websockets are
disabled' -> 403), before the Origin check even runs. HTTP polling GETs to
the same /socket.io/ route pass through (200), which is why the initial
handshake + REST bucket fetch worked but the live stream never connected.

The custom upgrade proxy in approuter/server.js is isLocal-gated, so this
only affected deployed envs (DEV/QA/PROD), not local `cds watch`.

Fix: add `"websockets": { "enabled": true }` (validated against the
approuter's own xs-app schema). Bump MTA patch version for the deploy.
@jung-thomas
jung-thomas merged commit 72e3996 into main Aug 12, 2026
3 of 4 checks passed
@jung-thomas
jung-thomas deleted the worktree-fix-event-display-websocket-403 branch August 13, 2026 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant