The customer-facing web/socket server of FaceKom. It serves the browser/kiosk/mobile-webview UI for the whole identification + video-banking journey (homepage → system-check → waiting-room → videochat, plus the self-service mobile flow), bundles the frontend assets, and brokers everything to the back-office via RabbitMQ. It is the public node a remote customer hits; the operator-side counterpart is vuer_oss.
Repo:/Users/levander/coding/facekom/vuer_css
Base branch:devel (CI audit.yaml runs on devel + customization/*; PR CI on every PR).
Package name / version:vuer_css1.9.11 (package.json:2-3); current devel-line tip commit is 62dad7f4 1.9.11.92.
Heavily customized via Git branches. devel is the core product; deployments ship from customization/<partner> branches (e.g. customization/raiffeisen, customization/instacash) that layer partner code into the customization/ tree. See customization-architecture and customization-branch-catalog.
1. Purpose & role in FaceKom
README (README.md:1) titles it “Facekom Client Side Server (CSS)”; package.json:4 describes it as “Vuer Client Side Server”.
It is a Node.js Express 5 web server + Socket.IO 4 realtime server that:
Renders Twig HTML pages and serves compiled JS/CSS bundles for the customer journey (server/web/routes.js, client/ui/pages/* — 25 immediate page dirs; the parallel customization/ui/pages/* tree adds 29 more).
Maintains the live WebRTC video session signalling over Socket.IO (server/socket/events/videochat*.js, server/transport/*).
Acts as an RPC/queue bridge to the rest of the FaceKom backend over RabbitMQ via @techteamer/mq (server.js:76-234).
Entry point server.js boots in this exact order (server.js:342-352):
config.loaded → rabbitmq() bootstrap → setupQueue → setupServices → setupCustomizations → StartWebServer → StartSocketServer.
2. Tech stack & runtime
Aspect
Value
Source
Language
JavaScript (CommonJS server; some bin tools in TypeScript)
engines.node >=22 but .browserslistrc still lists IE >= 11 — the browserslist drives client bundle transpilation targets, not the server runtime. Actual runtime browser support is enforced separately by the browsers.compatible/browsers.incompatible config (see §7).
Watch-mode build of externals+scripts+styles+server (no restart).
dev
node bin/watch/watch --restart
Dev server: watch + rebuild + supervisorctl restart vuer_css on server-file changes (README’s recommended yarn dev).
credits
node bin/credits > CREDITS.html
Generates an HTML license table of all deps.
validate_config:dev
node bin/validate-config.js config/dev.json
AJV-validates config/dev.json against the unified schema.
validate_config:docker
node bin/validate-config.js config/docker.json
Same for config/docker.json.
Entry points
Main:server.js (package.json:5"main": "server.js"). Run with node server.js / yarn start.
Web SDK demo server:web-sdk-demo/web-sdk-server.js — a separate small server (port 10084) subclassing WebServer with CSP disabled, for the embeddable Web SDK demo page.
No bin field in package.json; the bin/ dir holds build/dev tooling invoked via npm scripts (not installed CLIs).
Listening ports (hard-coded / config)
Web (Express):10083, bound to 127.0.0.1 (server.js:303-304).
Web SDK demo:10084 (web-sdk-demo/web-sdk-server.js:3).
Public traffic is fronted by nginx which proxies /socket.io→10082 and everything else→10083 (nginx_vuer_css_docker.conf:1-7,50-72).
Dockerfile / deployment
No Dockerfile exists in this repo (verified: find for Dockerfile/dockerfile returns nothing). Deployment is via supervisord + nginx, configured by the in-repo conf files. The Docker image is built elsewhere (not in this repo).
Deployment artifacts present:
supervisor_vuer_css_docker.conf — supervisord runs three programs: nginx, redis (/etc/redis/redis.conf), and vuer_css = node server.js with environment=NODE_ENV="docker", directory=/workspace/vuer_css, user=techteamer, autorestart=true, exitcodes=0,2, stopsignal=TERM.
supervisor_vuer_css_dev.conf — same shape but NODE_ENV=dev.
nginx_vuer_css_docker.conf / nginx_vuer_css_dev.conf — reverse proxy; docker listens :80, dev listens :30080/:30081, both with upstreams socketio-css 127.0.0.1:10082 and vuer-css 127.0.0.1:10083 (dev adds vuer-css-sdk 127.0.0.1:10084). Static root /workspace/vuer_css/web; client_max_body_size 25M; long-cache for /img /font /libs /icon /audio /js /css /entry.
NODE_ENV selects the getconfig file (config/dev.json / config/docker.json). config.js:41 additionally branches on NODE_ENV === 'dev' || 'travisci' to inject dev hostnames/CORS, and 'travisci' forces queue.url = amqp://localhost:5672.
4. Top-level structure
Path
Role
Verified
server.js
App entrypoint; wires queue, services, customizations, web + socket servers
Pushes --watch, then requires external.task, script.task, style.task, server.task (the watch entrypoint behind yarn watch/yarn dev).
bin/script/script.task.js
Declares esbuild bundle specs for client/ui/pages/**/*.script.js, customization/ui/pages/**, both **/layouts/**/*.layout.js → web/js; in dev/travisci also bundles client/ui/test/*.js; sets watch globs + livereload *.js.
bin/script/script.compiler.js
esbuild compiler: bundles a single .js entry → web/js/..., minify/sourcemap from config, keepNames:true; optionally injects esbuild-plugin-istanbul when build.istanbul.
bin/script/external.task.js
Bundle specs for client/externals/react/react.*.ext.js and react-dom.*.ext.js → web/js/externals (react-dom built with react marked external to avoid a 2nd React instance).
bin/script/external.compiler.js
browserify compiler that .require()s a module exposed under a name (react/react-dom) and .external()s excluded modules — produces the global require('react') shim.
ESM AJV validator: loads docs/config/main.json, resolves $ref sub-schemas from docs/config/, validates the JSON config passed as argv[2] (used by validate_config:* scripts); exits 1 on invalid.
bin/credits
#!/usr/bin/env node; uses license-checker to print an HTML <table> of all dependency licenses/URLs/publishers (piped to CREDITS.html).
bin/config.getter.ts
TS tool (Node strip-types or Deno): greps all .js for config.get('a.b.c') keys, diffs against config/ref.json, prints missing keys, regenerates config/ref.json (backs up to old_ref.json).
bin/doc.gen.ts
TS tool (Deno/Node shebang): inlines all $ref sub-schemas of docs/config/main.json, optionally writes docs/config/ref.json (-s), and generates Hungarian config docs markdown to docs/features/configuration/config-table.md.
bin/build/build.js, bin/style/style.compiler.js etc. import BrandingService and config.js at build time — the build reads the active config, so minify/generateSourceMaps/istanbul/branding all come from config/<env>.json (bin/build/build.js instantiates new BrandingService() and passes branding vars into the stylus compiler).
engines/build/restart.js is the restart helper used by dev: it spawns supervisorctl restart vuer_css and logs its output.
6. Key modules / services / entities
Bootstrap & DI
server/service_container.js — a ServiceBus extends WildEmitter singleton ({ emitter }) with addHook/removeHook/callHooks, registerOverride/callOverride, callOnlyHook. This is the customization seam: core code calls emitter.callOverride('routes:homepage', …, defaultFn) and callHooks('queue'|'services'|'routes'|'socket', …); customization/customizations.js registers the partner implementations. Everything (logger, queue, services, io, transport storages) is hung off this container.
server/bootstrap/connection/rabbitmq.js — builds a @techteamer/mqConnectionPool from config.queue, exits the process (code 2) if any RabbitMQ connection closes, and seeds serviceContainer.queue/rpcServer/rpcClient/queueServer/queueClient.
config.js — see §1/§7; also getSecureConfig() redacts security.sensitiveConfigFields (supports a.*.b wildcards) for safe exposure to clients.
Web layer (server/web/)
WebServer.js — builds the Express app: host-header allowlist (config.hosts), per-request nonce, kiosk detection (getKioskType), UA parsing (ua-parser-js) with per-browser isCompatible/isIncompatible flags + webrtc constraints, helmet security headers, a large custom CSP middleware (per-path sandbox/scriptSrc/connectSrc incl. wss://hosts.css), body-parser (25 MB JSON limit + application/csp-report), locale negotiation, Redis-backed session (separate kiosk session middleware), a SAML-bypass-via-redis middleware for ClientGate IdP, setupRoutes, CSP report endpoint, and 405/404/error handlers. Web app listens on 127.0.0.1:10083.
routes.js — central route table. Wraps most routes in csrfProtection (csurf) + serviceContainer.emitter.callOverride('routes:*', …) so customizations can replace any route. Mounts page routes (/, /lobby, /system-check, /videochat, /waiting-room, /feedback, /good-bye, /we-are-closed, /ui-kit), the self-service suite (/self-service/*, /api/self-service-*, SAML /saml/sso/*), device-change, appointment, WebSDK (/sdk/*, /web-sdk), Apple App-Site-Association, kiosk endpoints, and API validation via openapi-validator-middleware for /api/can-identify & /api/mobile/is-compatible. Disaster mode redirects all HTML to /we-are-closed.
socket-server.js — start() creates the socket.io server from config.web.socket; setupEvents() builds the module list (client, auth, pagevisit, lobby, waiting-room, echotest, ping, videochat + .presentation/.customerDataChange/.validation/.signature, webrtclog, mobile, selfservice, selfservice-v2, flow), lets customizations push extra event modules via callHooks('socket', modules), and on each connection invokes every module with the client. Supports socket.io v4 connection-state-recovery.
TransportPool.js, TransportSession.js, session/{Echo,Room,SelfService}TransportSession.js, storage/{Room,SelfService}TransportStorage.js — the media/room transport bridge between socket clients and the OSS/back-office over MQ (queue-transport-oss/rpc-transport-oss pair, set up in server.js:208-225).
Services (server/service/, instantiated in server.js:236-282)
CompatibilityService, SocketService, DisasterModeService, TurnPasswordService (TURN credential generation from webrtc.turn.secret), BrandingService (resource/value override with fallback — see below), DeviceChangeService, IpFilterService (per-action IP throttling/cooldown), PartnerService, SelfServiceRoomService, TokenService (JWT keystore via node-jose, loaded at boot), WaitingRoomGraceService, plus factory-style waiting-room.js / service-bus.js. DeviceChangeService/selfService services are conditional on config.
BrandingService.js — loads customization/branding-options.json defaults, overrides values from config.branding.values, and swaps resource paths to config.branding.resources when the file exists under web/ (else logs a warning and keeps the fallback). Branding vars are fed into the stylus build (§5).
Queue integration (server/queue/) — the FaceKom backbone
server.js:76-234 registers a large set of RabbitMQ participants through @techteamer/mq. Clients/servers are thin classes extending RPCClient/RPCServer/QueueClient/QueueServer (e.g. JwtAuth.js is 9 lines: class JwtAuth extends RPCClient { auth(token){ return this.call({jwtToken:token}) } }). Full queue catalog in §9.
client/features/webrtc/ — the Peer wrapper (read this before touching RTC APIs)
The client RTC layer is two levels deep, and this trips people up (verified during FKITDEV-8887):
SenderPeer and ReceiverPeer (client/features/webrtc/SenderPeer.js, ReceiverPeer.js) each construct this.pc = new Peer(config).
Peer (client/features/webrtc/Peer.js, extends WildEmitter) is itself a wrapper. It holds the real RTCPeerConnection as its own this.pc and only proxies a fixed set of methods — createOffer, setLocalDescription, setRemoteDescription, createAnswer, addStream, close — plus re-emits the icecandidate/track (and connection-state) events.
SenderPeer.pc / ReceiverPeer.pc is a Peer, not an RTCPeerConnection. The real connection is one more hop down, at senderPeer.pc.pc.
To use any RTCPeerConnection API that Peer does not already proxy (getSenders(), replaceTrack(), getStats(), addTrack, getTransceivers, restartIce, …) you must either:
Add a proxy method to Peer (preferred — matches the existing pattern, keeps callers wrapper-agnostic), or
reach through senderPeer.pc.pc explicitly.
Calling e.g. this.pc.getSenders() from inside SenderPeersilently returns nothing/false in production because Peer has no getSenders — no throw, just a no-op. This was the concrete bug in FKITDEV-8887.
Peer.startWatcher() is disabled — it return falses at the top, so the getStats watcher does not run. Don't assume stats polling is live.
Dictionary modules (*.trans.js) export dict.define({ key(){ return { hu:…, en:… } } }); loaded both server-side (twig global t) and browser-side. Default/supported locales come from config locales (dev.json: supported ['hu'], default hu).
7. Configuration
Mechanism:getconfig picks config/<NODE_ENV>.json (dev.json / docker.json); config.js wraps it. Access via config.get('a.b.c', default) and config.has('a.b.c'). springCloudConfigServer block (if present) pulls overrides from a Spring Cloud Config server with promise-retry (config.js:148-178).
DEV_DOMAIN — dev hostname; falls back to /etc/hostname (config.js:42-44).
TZ — force-set to Etc/UTC at boot (server.js:55).
NODE_TLS_REJECT_UNAUTHORIZED='0' — set when settings.allowSelfSignedCerts (server.js:57-59).
config/dev.json ships placeholder secrets (web.session.secret, jwt.secret, jwt.keystore[].k, webrtc.turn.secret, portal.headerToken.value). These are dev defaults only — never reuse in production. security.sensitiveConfigFields lists exactly which keys must be redacted before any config is exposed to a client (getSecureConfig).
8. Tests
Framework: Jest ^30 (config test/jest.config.js): rootDir = repo root, ignores node_modules|web|yarn-offline-cache, collectCoverage, coverageProvider: 'v8', lcov-only reporter → test/coverage/jest, testTimeout 30000, transform: {} (no transpile — tests run native).
Location:test/tests/unit/** — 115 .test.js files (glob test/tests/unit/**/*.test.js, recursive, excluding node_modules; repo-wide there are 116 — the extra one is customization/test/tests/unit/services/custom-compatibility-service.test.js), grouped: top-level (webserver.test.js, translations.test.js, device-change-info.test.js), api/* (19 endpoint tests), client/elements/*, client/engine/{message,radio,view}/*, client/features/{system-check,videochat,webrtc,time-slot-picker}/*. Customization tests live under customization/test/ (coverage-ignored).
How to run:yarn test:unit (= yarn jest unit/* --forceExit) or a single suite yarn jest <testfile> (README). Requires the test_resources repo cloned to /workspace/test_resources (README:7).
test/testconfigs/local.json overrides build/test config for the test env (build.istanbul:true, generateSourceMaps:false, geolocation, socket origins).
CI:.github/workflows/pull-request.yaml runs yarn lint + yarn build on Node 22 & 24 for every PR. .github/workflows/audit.yaml (push to devel/customization/*) runs yarn audit + improved-yarn-audit --min-severity critical (with 3 excluded GHSAs) and fails if executable files appear outside allowed dirs. test/travis.sh is the legacy Travis equivalent (lint/build/audit/git diff --exit-code).
No jest/mocha test runner config beyond test/jest.config.js; PR CI does not run the test suite — it only lints and builds. Tests are expected to be run locally / elsewhere.
9. Dependencies on other FaceKom services (evidenced in code)
All inter-service comms go over RabbitMQ via @techteamer/mq (mutual-TLS amqps). Registered in server.js:
RPC servers exposed BY css (server.js:81-90, 215-217): css-ping, rpc-system-oss, rpc-transport-css, and (if features.documentScanner) kiosk-document-scan.
TURN/STUN + Janus — WebRTC media servers referenced in webrtc.* config (URLs are sensitiveConfigFields).
Spring Cloud Config server — optional remote config (config.js:148).
No direct SQL/Mongo client in this repo — all persistence is delegated to backend services over MQ.
10. Verified gotchas
Process exits on RabbitMQ disconnect.server/bootstrap/connection/rabbitmq.js:14-19 registers connection.on('close') → process.exit(2) for every connection. Combined with supervisord autorestart=true, exitcodes=0,2, a flaky MQ connection causes full restarts. Boot also process.exit(2) on any startup error (server.js:349-352).
Web + socket bind to 127.0.0.1 only. Ports 10082/10083 are loopback-bound (server.js:304,322-325); the app is not reachable except through nginx. Direct access in dev requires the nginx/supervisor stack or port-mapping.
No Dockerfile / no test step in PR CI. The image is built outside this repo, and PR CI (pull-request.yaml) only runs lint + build — green CI does not mean tests passed.
Read SonarCloud PR issues without a Sonar token (via GitHub check-run annotations). SonarCloud (sonar-project.properties: projectKey vuer-css, org techteamer, host sonarcloud.io) runs on each PR (the pull-request.yaml path) and posts every issue as a GitHub check-run annotation on the PR head commit + a summary PR comment from the sonarqubecloud bot. So the full issue list is reachable through gh/the GitHub API alone — no SonarCloud login:
gh pr view <n> --repo techteamer/vuer_css --json statusCheckRollup,headRefOid # find "SonarCloud Code Analysis" check + head SHAgh api repos/techteamer/vuer_css/commits/<sha>/check-runs \ --jq '.check_runs[] | {id, name, ann: .output.annotations_count}' # SonarCloud run id + annotation countgh api repos/techteamer/vuer_css/check-runs/<id>/annotations # each issue: path, start_line, level, message + deep-link
Gotchas: (1) a passing Quality Gate ≠ zero issues — the gate only checks new-code threshold metrics (coverage/duplication/rating), so it can be GREEN with many code-smell “New issues”; (2) there is no analyzed devel baseline (only PRs run Sonar), so Sonar diffs against nothing and can attribute pre-existing smells in a touched file to the PR — confirm real authorship with gh pr diff <n>; (3) an annotation’s annotation_level (notice/warning/failure) is the issue severity, NOT the gate verdict. (Technique used in FKITDEV-8887.)
Customization seam is implicit. Routes/queues/services/socket-events are swappable at runtime via serviceContainer.emitter overrides/hooks declared in customization/customizations.js. On a customization/<partner> branch the actual behavior of /, /lobby, /feedback, /good-bye, /we-are-closed, /system-check, /waiting-room, /dch/:token, submit-feedback etc. may be replaced — always check customization/ before assuming core route logic. See customization-architecture.
CSP is hand-rolled and path-dependent.WebServer.cspMiddleware skips CSP entirely for IE/Safari and mutates sandbox/scriptSrc/connectSrc/imgSrc per request path; dev mode adds 'unsafe-eval' and livereload origins. Changing a route path can silently break its CSP. The Web SDK demo server disables CSP completely.
Build reads runtime config. Bundling pulls minify/sourcemaps/istanbul/branding from config/<env>.json at build time. Building under the wrong NODE_ENV produces wrong assets (e.g. dev builds inject istanbul coverage + 'unsafe-eval').
Mixed module systems. Server is CommonJS (require); bin/validate-config.js and bin/*.ts are ESM/TypeScript run via Node strip-types or Deno (see their shebangs). eslint.config.mjs is the flat-config ESLint.
Client RTC peers are a wrapper-in-a-wrapper.SenderPeer.pc/ReceiverPeer.pc is a Peer (a WildEmitter proxy), not an RTCPeerConnection; the real connection is at …pc.pc, and Peer only proxies createOffer/setLocalDescription/setRemoteDescription/createAnswer/addStream/close. Any other RTC API (getSenders, replaceTrack, getStats, …) is a silent no-op unless you add a Peer proxy or reach through pc.pc. Full detail: [[vuer_css#clientfeatureswebrtc--the-peer-wrapper-read-this-before-touching-rtc-apis|client/features/webrtc/ — the Peer wrapper (read this before touching RTC APIs)]]. (Found in FKITDEV-8887.)
A unit test that stubs peer.pc = { … } can MASK an RTC bug. Tests built via Object.create(SenderPeer.prototype) + peer.pc = { getSenders: () => [...] } assert against a shape production never creates (production peer.pc is a Peer, not the mock). Such a test passes while the real method no-ops in prod — exactly what hid the FKITDEV-8887 regression. Tests for these methods must drive the real SenderPeer → Peer → RTCPeerConnection chain: construct a real SenderPeer/Peer with global.RTCPeerConnection mocked to expose the needed API (getSenders, etc.). See test/tests/unit/client/features/webrtc/.
Unverified / gaps
Surveyed structurally, not line-by-line: the full client/ tree (25 immediate page dirs under client/ui/pages/ × ~6-10 files each, client/engine/*, client/features/*), the 30 server/web/api/*.js + 35 server/web/routes/*.js endpoint files, all ~18 server/socket/events/*, all ~60 queue client/server classes, the customization/ partner code (only customizations.js, BrandingService inputs, and the customization/server file listing were read), and the 115 test files. I read representative examples of each category (e.g. JwtAuth.js, ping.js, videochat/ page listing) and confirmed the directory maps by listing; individual endpoint/handler internals are not documented here.
engines/translator/Dictionary.js read only first ~60 lines (enough for the translation contract); remaining methods not detailed.
Customization branches (customization/raiffeisen, customization/instacash, etc.) were not checked out or diffed — this doc describes the devel-line core as present in the working tree (currently on branch bugfix/FKITDEV-8787-..., tip 1.9.11.92). Partner-specific overrides are out of scope here; see customization-branch-catalog.
Web SDK demo (web-sdk-demo/web-sdk-server.js) read only first ~20 lines.
No Dockerfile and no DB/Redis client config beyond what config/code reference — image-build and infra provisioning live outside this repo.