vuer_css (Client Side Server)

Role

FaceKom’s customer-facing dual-server Node.js service. Handles real-time video communication, waiting rooms, self-service identity verification flows, and customer portal integration. Communicates with vuer_oss exclusively via RabbitMQ RPC — has no direct database access.

PropertyValue
Version1.9.11
RuntimeNode.js >= 22.0.0
FrameworkExpress 5 + Socket.IO 4 + React 18 + custom MVC engine
RepoTechTeamer/vuer_css
Path (remote)/workspace/vuer_css
Path (local mount)/Users/levander/coding/mnt/Facekom/vuer_css

Architecture Overview

vuer_css is a dual-server architecture: an Express HTTP server and a separate Socket.IO server sharing a common service layer, with all business logic delegated to vuer_oss over RabbitMQ.

graph TB
    Browser["Customer Browser"]
    Nginx["Nginx :30080/:20080"]
    Express["Express HTTP :10083"]
    SocketIO["Socket.IO :10082"]
    Services["Shared Services Layer"]
    RabbitMQ["RabbitMQ"]
    OSS["[[vuer_oss]]"]
    Janus["Janus WebRTC Gateway"]
    Redis["Redis (sessions)"]

    Browser -->|HTTPS| Nginx
    Nginx --> Express
    Nginx --> SocketIO
    Express --> Services
    SocketIO --> Services
    Services -->|RPC + Queue| RabbitMQ
    RabbitMQ --> OSS
    Services -->|TransportPool| Janus
    Express -->|Session store| Redis
    SocketIO -->|Token verify| Redis

Ports

PortService
:10083Express HTTP (bound to 127.0.0.1, behind Nginx)
:10082Socket.IO
:10084Web SDK demo (optional)
:30080Nginx reverse proxy (dev)

What It Does NOT Have

Unlike vuer_oss, vuer_css intentionally lacks:

  • Database layer (no Sequelize, no direct DB access)
  • Authentication strategies (no Passport)
  • Document processing (no PDF/DOCX generation)
  • Computer vision services
  • Cron jobs, background workers, media conversion
  • Email/SMS services

All business logic is delegated to vuer_oss via RabbitMQ.


Entry Point: server.js

File: /workspace/vuer_css/server.js (353 lines)

Startup Sequence (strictly ordered promise chain)

graph LR
    A["config.loaded"] --> B["connectRabbitMQ()"]
    B --> C["setupQueue()"]
    C --> D["setupServices()"]
    D --> E["setupCustomizations()"]
    E --> F["startWebServer(:10083)"]
    F --> G["startSocketServer(:10082)"]

Crash = Fast Restart

If any startup step fails, the process calls process.exit(2). Supervisor restarts it in 1-2 seconds.

Process Setup

  • Error handlers: uncaughtException, unhandledRejection, exit, optional warning
  • Signal handlers: SIGINT exit 130, SIGTERM exit 143
  • Timezone: Forces process.env.TZ = 'Etc/UTC'
  • Self-signed certs: NODE_TLS_REJECT_UNAUTHORIZED = '0' when settings.allowSelfSignedCerts is true

Dependencies Registered at Startup

TypeCountExamples
RPC Servers3css-ping, rpc-system-oss, kiosk-document-scan (conditional)
RPC Clients23+ coreSee
Queue Servers5+service-bus, waiting-room, videochat-css, selfservice-css
Queue Clients9+feedback, videochatOss, customerHistory, webrtclog
TransportPool1Bidirectional queue+RPC for transport-oss/transport-css

Hacks in server.js

  • Lazy logger require: Logger is required inside catch blocks to avoid circular deps
  • Mixed config access: Lines 145/150 use config.deviceChange (direct) vs config.get('features.selfService') (safe). Latent bug if config shape changes.
  • Dead code: Lines 158-159: customerDocuments and flowDocuments RPC clients marked “TODO: remove? Never used…”

Configuration: config.js

File: /workspace/vuer_css/config.js (208 lines)

Loads JSON config via getconfig library, adds safe accessor methods, handles Spring Cloud Config Server integration.

Key Patterns

MethodBehavior
config.get(path, default)Safe dot-path accessor. Returns defaultValue if any segment missing
config.has(path)Proper hasOwnProperty check (correctly handles falsy values)
config.loadedPromise that resolves after Spring Cloud Config loads
config.getSecureConfig()Returns config copy with sensitive fields replaced with 'SECRET'. Supports wildcard paths

config.get() Bug

config.get() treats 0 and '' (empty string) as missing values due to a !current[path[0]] check. Use config.has() for falsy-but-valid values.

Dev Environment Auto-Config

  • Reads /etc/hostname for dev domain
  • Auto-sets hosts.css, hosts.webSdkDemo, hosts.oss from hostname
  • Adds CORS origin for Web SDK in dev

CORS Security Defaults

  • If web.cors.origin === '*', replaces with config.hosts.css
  • If web.socket.cors.origin === '*', replaces with config.hosts.css + ':443'
  • Handles backwards compatibility with Socket.IO v2 “origins” config

Service Container & Event Bus

File: /workspace/vuer_css/server/service_container.js (76 lines)

Central dependency injection container and event bus. ServiceBus extends WildEmitter and provides:

MechanismDescriptionExamples
Hooks (addHook/callHooks)Named command tags, multiple handlers via Promise.all'queue', 'services', 'routes', 'socket', 'pagevisit', 'template:custom-content:*', 'webrtc:media-options'
Overrides (registerOverride/callOverride)Customization replaces default behavior. Last arg is always the default fn'routes:homepage', 'routes:waiting-room'
Events (WildEmitter)Standard pub/sub'videochat:*', 'selfService:*', 'disaster:*', 'waiting-room:*', 'service-bus:*'

Runtime Properties

The container is a plain object { emitter } that gets properties dynamically added during startup:

serviceContainer.emitter              // ServiceBus instance
serviceContainer.socketTokenStorage   // JWT token storage
serviceContainer.logger
serviceContainer.serverDiagnostic
serviceContainer.queueConnectionPool
serviceContainer.queue
serviceContainer.rpcServer.*          // 3+ RPC server instances
serviceContainer.rpcClient.*          // 23+ RPC client instances
serviceContainer.queueServer.*        // 5+ queue server instances
serviceContainer.queueClient.*        // 9+ queue client instances
serviceContainer.transportPool
serviceContainer.roomTransportStorage
serviceContainer.selfServiceTransportStorage
serviceContainer.service.*            // 11+ service instances
serviceContainer.app                  // Express app
serviceContainer.sessionStore         // Redis session store
serviceContainer.io                   // Socket.IO instance
serviceContainer.customizations       // Customer-specific extensions

No Type Safety

Any module can mutate the container. callOnlyHook silently ignores additional hooks if multiple were registered (only calls providers[0]).


Web Server: WebServer.js

File: /workspace/vuer_css/server/web/WebServer.js (553 lines)

Setup Chain (fluent builder)

setupLocale() -> setupRedis() -> setupSaml() -> setupSession() ->
setupTwig() -> setupRoutes() -> setupCSPReportViolation() ->
setup405Handler() -> setup404Handler() -> setupErrorHandler()

Middleware Stack (in order)

#MiddlewarePurpose
1Host header validationBlocks requests where req.get('host') not in config.hosts
2CSP nonce generationcrypto.randomUUID() per request
3UA parsingFull browser detection via ua-parser-js, adds req.browser with isIOS/isChrome/isSafari + WebRTC constraints
4HelmetConfigurable security headers
5NoCacheAdds no-cache headers to all responses
6Custom CSPPer-path CSP rules (see )
7Body parserurlencoded + JSON (25MB limit) + CSP report JSON
8Cookie parser
9Locale middlewareCookie-based + query-string locale override
10Redis clientConnection established
11SAML bypassCaches SAMLResponse in Redis for cross-domain cookie workaround
12Session middlewareRedis-backed, separate configs for kiosk vs regular
13Kiosk detectionCustom User-Agent check for FaceKomPont kiosks
14Session retryUp to 3 attempts to establish session

CSP Policy

  • Default: Strict CSP with nonces, sandbox (allow-forms, allow-scripts, allow-same-origin, allow-modals)
  • Dev mode: Adds 'unsafe-eval' for Istanbul coverage
  • Web SDK page: Adds 'unsafe-inline' to styleSrc
  • Videochat + presentation: Adds blob: to imgSrc for PDF.js
  • Google: *.google.com and *.gstatic.com always in defaultSrc and scriptSrc
  • IE/Safari: CSP middleware completely bypassed (returns next())

SAML Integration

Clever workaround for cross-domain cookie issues:

sequenceDiagram
    participant IdP as SAML IdP
    participant Browser
    participant CSS as vuer_css
    participant Redis

    IdP->>CSS: POST /saml/sso/clientGateLogin (SAMLResponse)
    Note over CSS: Cross-origin, no session cookie
    CSS->>Redis: Store SAMLResponse with UUID key (60s TTL)
    CSS->>Browser: 302 Redirect with ?samlResponseCacheId=UUID
    Browser->>CSS: GET same URL (same-origin, cookie sent)
    CSS->>Redis: Retrieve SAML data by UUID
    CSS->>Browser: Process auth, return page

Session Config

  • Regular sessions: Redis-backed, httpOnly, secure, configurable sameSite/maxAge
  • Kiosk sessions: Separate session config for kiosk devices
  • iOS 12 fix: Overrides sameSite to false for Apple Mobile major version 12
  • Session retry: If session fails to initialize, retries up to 3 times

WebServer.js Hacks

  • iOS 12 sameSite fix: Monkey-patches sessionStore.generate to override cookie sameSite
  • Error handler: Wrong signature ((err, req, res) — missing next param). Express 5 may not call it correctly.
  • Kiosk info loss: req.kioskInfo is set to !!kioskInfo (boolean), losing the actual info object. Full object is in req.session.kiosk.kioskInfo.

Routing: routes.js

File: /workspace/vuer_css/server/web/routes.js (295 lines)

Every route is wrapped in serviceContainer.emitter.callOverride('routes:NAME', {...}, defaultFn), allowing the customization layer to completely replace any route handler.

Page Routes (GET, CSRF-protected)

RouteHandlerNotes
/homepage.endpointkioskCompatibility + allowIfWeAreOpen
/404404.endpoint
/feedbackfeedback.endpoint
/good-byegood-bye.endpoint
/lobbylobby.endpoint
/system-checksystem-check.endpoint
/videochatvideochat.endpoint
/waiting-roomwaiting-room.endpoint
/waiting-room-exitwaiting-room-exit.endpoint
/we-are-closedwe-are-closed.endpoint
/ui-kitui-kit.endpointOnly if build.uikit
/web-sdkweb-sdk.endpointOnly if WebSDK enabled
/appointmentappointment-page.endpointOnly if appointment.enabled
/appointment-waitingappointment-waiting-page.endpoint+ allowIfWeAreOpen

Self-Service Routes (conditional: features.selfService && selfService.web)

RouteHandlerNotes
/self-service/loginself-service-login.endpoint+ phase redirect
/self-service/auth-2-factorself-service-auth-2-factor.endpoint+ phase redirect
/self-service/client-gateself-service-client-gate.endpoint+ phase redirect
/self-service/preparationself-service-preparation.endpoint+ phase redirect
/self-service/consent-pepself-service-consent-pep.endpoint+ phase redirect
/self-service/consent-ttnyself-service-consent-ttny.endpoint+ phase redirect
/self-service/wrapupself-service-wrapup.endpoint
/self-service/abortself-service-abort.endpoint
/self-serviceself-service.endpoint+ phase redirect
/saml/sso/metadata.xmlsaml-metadata.endpoint
/saml/sso/clientGateLoginclient-gate-login.endpoint+ SAML middleware

API Routes (POST, CSRF-protected unless noted)

RouteHandlerNotes
/api/pre-checkpre-check (GET)
/api/sentrysentryClient error reporting
/api/submit-feedbacksubmit-feedback
/api/submit-registrationsubmit-registrationIf features.registration
/api/device-change/emaildevice-change-email+ IP filter
/api/device-change/smsdevice-change-sms+ IP filter
/api/device-summarydevice-summary
/api/request-callbackrequest-callback
/api/document-uploaddocument-uploadIf documentUpload
/api/document-deletedocument-deleteIf documentUpload
/api/can-identifycan-identifyOpenAPI validated
/api/mobile/is-compatibleis-compatibleOpenAPI validated
/api/mobile/settingsmobile-settings
/api/presentation-document/:idpresentation-documentIf features.presentationMode
/api/self-service-*VariousMultiple self-service API endpoints
/sdk/translationssdk-translationsWebSDK only, CORS
/kiosk/pingkioskPing.endpoint
/api/css-pingcss-ping-waiting-room.endpoint

Disaster Mode

handleDisaster() middleware is applied globally:

  • /we-are-closed passes through
  • HTML requests redirect to /we-are-closed
  • API requests get 400 with body 'disaster'

CSRF Bypass

submit-appointment route has CSRF protection commented out (routes.js line 254). See security-audit.

CORS Race Condition

WebSDK CORS handler mutates shared corsOptions.origin based on Referer header. Under concurrent requests, one request’s CORS origin could leak to another.


Socket.IO System

Socket Server Setup

File: /workspace/vuer_css/server/socket/socket-server.js (49 lines)

Loads 14+ event modules plus any custom modules via hook:

client, auth, pagevisit, lobby, waiting-room, echotest, ping,
videochat, videochat.presentation, videochat.customerDataChange,
videochat.validation, webrtclog, mobile, selfservice, selfservice-v2, flow

Socket Authentication

File: /workspace/vuer_css/server/socket/events/auth.js (39 lines)

sequenceDiagram
    participant Client as Browser
    participant Socket as Socket.IO Server
    participant Token as SocketTokenStorage
    participant Redis as Redis Sessions

    Client->>Socket: auth:auth (JWT token + socketLabel)
    Socket->>Token: verifyToken(JWT)
    Note over Token: JWT has 2-min expiry
    Token-->>Socket: { sessionId }
    Socket->>Redis: sessionStore.get(sessionId)
    Redis-->>Socket: sessionData
    Note over Socket: Delete cookie from session, attach to client.sessionData
    Socket-->>Client: auth:ok

On error: returns 'reload' (triggers page reload) or 'disaster' (redirects to /we-are-closed).

Key Socket Events

Videochat Events (638 lines)

Client-initiated (all auth-gated via auth.doIfHasRoom):

EventPurpose
videochat:joinJoin room, get room data (operator IDs stripped before sending to client)
videochat:senderPeer:init/start/candidateWebRTC peer setup
videochat:receivingPeer:candidateReceiving peer ICE candidates
videochat:chat:message/logChat messaging
videochat:selfMuteSelf-mute toggle (blocked in kiosk mode)
videochat:highContrastHigh contrast toggle
videochat:localVideoTrackStatusCamera on/off
videochat:closeClose room
videochat:check_room_aliveKiosk alive check
videochat:screen:resize/deviceorientationScreen metrics
videochat:camera:values:completeCamera capabilities

Server-initiated (via ServiceBus events):

EventPurpose
videochat:inviteInvites customer from waiting room (with timeout)
videochat:leave/join/closeRoom state changes
videochat:chat:message/fontSizeChat from operator
videochat:flow:changeFlow state changes
videochat:promptSmsTokenSMS verification prompt
videochat:selfMute/selfHold/highContrastRemote state changes
videochat:receivingPeer:start/stopMedia negotiation
videochat:screenshot:remoteRemote screenshot capture
videochat:camera:profile/get:values/set:valueCamera control

Duplicate Session Prevention

When a videochat pagevisit is detected, the system finds any existing socket clients with the same customerId on the videochat page and redirects them away (lines 609-627).

Self-Service Events (371 lines)

Full self-service lifecycle via socket:

EventPurpose
selfService:startCreate self-service room
selfService:getPhase/getNextPhase/getStepsFlow navigation
selfService:getPeerConnectionConfigWebRTC setup
selfService:peer:init/start/candidateWebRTC peer management
selfService:closeCallEnd call
selfService:screenshot-save/screenshot-recognizePhoto capture + ML recognition
selfService:sendVerificationSms/verifySmsTokenSMS 2FA
selfService:authLoginCredentialsLogin auth
selfService:reportConsentResultConsent tracking
selfService:reportStepSkipStep skip reporting
selfService:emrtd-infoeMRTD (electronic passport) data

Waiting Room Events (41 lines)

  • join-waiting-room — Joins queue via RPC, returns queue position. Uses grace period service to handle reconnects.
  • disconnect — Queues leave operation with grace period (allows brief disconnects without losing queue position).

Lobby Events (26 lines)

  • systemCheck:results — Saves system check results to customer history. Size-limited to 24KB.

RabbitMQ Queue Layer

All queue classes extend @techteamer/mq base classes. See rabbitmq-communication for the full RabbitMQ architecture.

RPC Clients (request-response, to vuer_oss)

Queue NameClient ClassPurpose
rpc-create-customerCreateCustomerCreate customer record
rpc-customer-portal-dataPortalDataGet portal data
rpc-jwt-authJwtAuthJWT authentication
rpc-get-customerGetCustomerRetrieve customer
rpc-waiting-roomWaitingRoomJoin/leave waiting room
rpc-videochat-ossVideoChatOssVideochat room operations
rpc-callback-requestRequestCallbackRequest callback
rpc-openhoursOpenHoursGet open hours
rpc-openhours-calendarsOpenHoursCalendarsCalendar management
rpc-partner-servicePartnerServicePartner data
rpc-custom-contentCustomContentDynamic content
rpc-selfservice-actionsSelfServiceActionsSelf-service operations
rpc-selfservice-uploadSelfServiceUploadScreenshot upload/recognize
client-gateClientGateClient gate auth
background-room-exportRoomExportRoom data export
background-self-service-room-exportSelfServiceRoomExportSelf-service export
rpc-system-checkSystemCheckSystem compatibility check
rpc-jwt-kiosk-authJwtKioskAuthKiosk JWT auth
rpc-kiosk-alive-checkKioskAliveCheckKiosk heartbeat
rpc-media-contentMediaContentMedia content (hold screen)
rpc-identification-routerIdentificationRouterRoute identification
rpc-device-compatibility-checkDeviceCompatibilityCheckDevice compat
rpc-appointmentAppointmentAppointment management
rpc-device-changeDeviceChange(conditional)
rpc-document-upload/deleteDocumentUpload/Delete(conditional)
rpc-presentationPresentation(conditional)
rpc-selfservice-v2SelfServiceV2(conditional)

RPC Servers (receive from vuer_oss)

Queue NameServer ClassPurpose
css-pingPingHealth check
rpc-system-ossSystemOssSystem commands from OSS
kiosk-document-scanDocumentScanRPCServerDocument scan results
rpc-transport-cssTransportCssTransport layer messages

Queue Servers (fire-and-forget, from vuer_oss)

Queue NameServer ClassPurpose
queue-service-busServiceBusOSS started, disaster on/off
queue-waiting-roomWaitingRoomQueue position updates
queue-videochat-cssVideoChatCssVideochat events (join/leave/close)
queue-selfservice-cssSelfServiceCssSelf-service events
queue-selfservice-v2-cssSelfServiceV2CssV2 events
queue-transport-cssTransportCssTransport messages

Queue Clients (fire-and-forget, to vuer_oss)

Queue NameClient ClassPurpose
queue-feedbackFeedbackSubmit feedback
queue-videochat-ossVideoChatOssJoin/leave/resize/orientation
queue-customer-historyCustomerHistoryLog customer events
queue-webrtc-logWebRTCLogWebRTC diagnostics
queue-clienterror-logClientErrorLogClient error reports
queue-customerCustomerCustomer updates
queue-selfservice-eventsSelfServiceEventsSelf-service events
queue-selfservice-v2SelfServiceV2V2 events
css-client-ping-resultClientPingResultPing results
queue-transport-ossTransportOssTransport messages

Transport Layer (Janus/WebRTC Relay)

TransportPool

File: /workspace/vuer_css/server/transport/TransportPool.js (236 lines)

Manages bidirectional communication between CSS and OSS for media transport (Janus WebRTC gateway). Each videochat room gets a TransportSession.

graph LR
    CSS["vuer_css"] -->|"register/transaction/send/destroy"| Pool["TransportPool"]
    Pool -->|RPC| OSS["vuer_oss"]
    Pool -->|Queue| OSS
    OSS -->|"via Janus"| WebRTC["WebRTC Media"]
ProtocolPurpose
registerRegister a new session
transactionRequest-response over RPC
sendFire-and-forget over queue
destroyCleanup session

Auto-registration: If a transaction arrives for an unknown session with autoRegister data, dynamically loads the session class from ./session/ directory. Path traversal protection validates sessionClassPath starts with sessionDirectoryPath.

Session Types

TypePurpose
RoomTransportSessionVideochat room media transport (sender/receiver peer negotiation, chat, mute, hold, screenshots, camera control)
SelfServiceTransportSessionSelf-service media transport (peer init/start/candidate, close, Janus info)
EchoTransportSessionEcho test for system checks

Storage

  • RoomTransportStorage: Maps roomId RoomTransportSession
  • SelfServiceTransportStorage: Maps roomId SelfServiceTransportSession

Server Services

TokenService

File: /workspace/vuer_css/server/service/TokenService.js (31 lines)

  • JWE encryption/decryption using node-jose
  • Keystore loaded from config at startup
  • Uses A256GCM content algorithm, compact format
  • Default key ID: 'facekom'

SocketTokenStorage

File: /workspace/vuer_css/server/SocketTokenStorage.js (21 lines)

  • Creates JWT tokens with { sessionId } payload, 2-minute expiry
  • Used to authenticate Socket.IO connections
  • Tokens created in Template.render() and passed to client via data-socketToken attribute

Shared Secret

Uses config.get('jwt.secret') for signing — shared secret, not per-session. Compromise of this secret allows forging socket auth tokens.

SocketService

File: /workspace/vuer_css/server/service/SocketService.js (97 lines)

  • findClient(cb) — Find socket client matching predicate
  • filterClients(cb) — Filter all connected clients
  • getClientForRoom(roomId) — Find client in specific videochat room
  • getClientForSelfServiceRoom(roomId) — Find client in self-service room
  • startHttpHeartbeat() — Periodic check for stale waiting room customers. If lastPing + forceDisconnectMs < now, forces leave.

BrandingService

File: /workspace/vuer_css/server/service/BrandingService.js (79 lines)

  • Loads branding defaults from customization/branding-options.json
  • Overrides with config-specified values and resource paths
  • Validates branding resource files exist on disk
  • Used during both runtime and build-time

CompatibilityService

  • Browser compatibility rule checking against config
  • Rules define compatible/incompatible browser+OS combinations
  • Used in UA parsing middleware

DisasterModeService

File: /workspace/vuer_css/server/service/DisasterModeService.js (48 lines)

  • Toggles via ServiceBus events from vuer_oss (service-bus:disaster:on/off)
  • When activated, broadcasts disaster:on to ALL connected socket clients
  • Client-side redirects to /we-are-closed
  • HTTP requests redirected by handleDisaster() middleware

TurnPasswordService

File: /workspace/vuer_css/server/service/TurnPasswordService.js (131 lines)

  • Generates time-limited TURN server credentials using HMAC-SHA1
  • Supports per-Janus-server ICE server filtering
  • Strips custom keys (janusServers, secret) before sending to client

IpFilterService

File: /workspace/vuer_css/server/service/IpFilterService.js (151 lines)

  • Rate limiting by IP + tag combination
  • Configurable: throttlePeriodMs, suppressAfterAttempts, coolDownPeriodMs
  • Two modes: createIpFilter (block if already filtered) and createIpMonitorAndFilter (monitor + block)

Stealth Rate Limiting

Returns HTTP 200 with error codes ERROR_FKIPFT01/ERROR_FKIPFT02 instead of 429. This is intentional — avoids exposing rate limiting behavior to attackers.

Other Services

ServicePurpose
WaitingRoomGraceServiceGrace period for disconnected waiting room customers (reconnect without losing queue position)
PartnerServicePartner-related business logic
SelfServiceRoomServiceSelf-service room management (conditional: selfService.web)
DeviceChangeServiceDevice change flow management (conditional: deviceChange)
waiting-room.jsListens for waiting-room:changed events, broadcasts queue position updates
service-bus.jsServiceBus queue message handling

Server Middleware

MiddlewareFilePurpose
we-are-open.jsserver/web/middleware/we-are-open.jsChecks open hours via RPC. Redirects to /we-are-closed. Cookie bypass: bypassopenhours=1. Appointments get early access via beforeOpeningWaitingRoomAvailableSec.
saml-middleware.jsserver/web/middleware/saml-middleware.jsSAML SSO authentication
self-service-phase-redirect.jsserver/web/middleware/self-service-phase-redirect.jsEnsures customer is in correct self-service phase, redirects if wrong
kiosk-compatiblity.jsserver/web/middleware/kiosk-compatiblity.jsKiosk device compatibility check (note: typo in filename — missing ‘i’ in compatibility)

Server Utilities

UtilityPurpose
magic.jsMIME type detection via wasmagic (WebAssembly libmagic) for screenshot upload validation
userAgent.jsUA parsing utilities
deviceChangeHandler.jsDevice change form processing
formatOpenHours.jsOpen hours formatting
getKioskType.jsExtracts kiosk type from User-Agent string
lobbyRoomRedirect.jsLobby room redirect logic
validateFaceKomPontRequest.jsValidates FaceKomPont kiosk auth tokens

Client Engine (Custom MVC Framework)

Not React

This is a sophisticated custom client-side MVC framework. React is used only for specific newer components. Most of the UI is built with this engine.

View

File: client/engine/view/View.js (751 lines) — The core UI primitive.

FeatureDescription
Element-to-instance mappingGlobal elementToInstance Map tracks which View manages which DOM element
Static factory methodscreate(), mount(), mountIn(), mountAll(), mountAllIn()
Template renderingStatic render() uses Template class to create HTML
CSS class generationAuto-generates class selectors from prototype chain via fullClassId
Lifecycle hooksonCreate, onAttaching, onAttached, onDetaching, onDetached, onDestroyed
DOM manipulationappendTo, prependTo, detachElement, removeElement
Event systemaction() creates event listeners, dispatch() fires CustomEvents, relay()/intent() for promise-based request-response
Layout systemdata-layout attribute management
Action proxyproxyActions() for delegation

Template (Client-Side)

File: client/engine/view/Template.js (386 lines) — Custom template engine (NOT Twig).

FeatureDescription
Custom tags<block>, <content>, <super>, <parent>
Import systemCustom element tags resolved via static get imports() array
Attribute extractionType-aware parsing (String, Number, Boolean, JSON)
Child directivechild="propertyName" auto-assigns rendered elements to view properties
Array childrenchild="items[]" for array-based child directives
Inheritance<super> tag renders parent view’s template with content slots
Renderingelement.innerHTML = output — the core rendering mechanism

Other Engine Components

ComponentFilePurpose
Controllerclient/engine/controller/Controller.js (34 lines)Minimal MVC controller. static control(view) factory. addModule(controller) for composition
Radioclient/engine/radio/Radio.jsNamed channel management (create, subscribe, publish, peek, unsubscribe)
Channelclient/engine/radio/Channel.jsSubscriber management with Listener wrappers. publish() short-circuits on first non-null return
Serviceclient/engine/service/Service.jsSingleton pattern (constructor returns existing instance)
ServiceManagerclient/engine/service/ServiceManager.jsStatic registry of services by name
Actionclient/engine/view/Action.jsEvent listener management with auto-cleanup
AttributeSetclient/engine/view/AttributeSet.jsReactive attribute management
DataSetclient/engine/view/DataSet.jsDataset attribute management
SwitchStateclient/engine/view/SwitchState.jsBinary state management
ViewStateclient/engine/view/ViewState.jsView state management

Client Features

Authentication (client/features/auth.js, 63 lines)

  1. Reads data-socketToken and data-page-visit-id from document.body
  2. Connects Socket.IO via SocketService
  3. Emits auth:auth with token + socketLabel
  4. On error: 'reload' location.reload(), 'disaster' redirect to /we-are-closed
  5. On reconnect: automatic page reload
  6. Firefox fix: Disables reload during beforeunload to prevent stale reload

SocketService (client/features/socket/SocketService.js, 130 lines)

Singleton client-side Socket.IO wrapper:

  • Reads data-socketioSettings from document.body (JSON)
  • Lazy connection via connect()
  • Handles disconnect, reload, redirect, disaster:on/off, ping events
  • Global exposure: window.socketio = this.connection (debugging convenience)
  • Deferred emit()/on()/once() — queues calls until connection established

WebRTC Module (client/features/webrtc/)

ComponentPurpose
Peer.js (214 lines)Base WebRTC peer connection wrapper using WildEmitter. Firefox 56 SDP fix for VP8/VP9. Stat watcher disabled (“DO NOT START IT, this only works for chrome now”).
SenderPeer.js (116 lines)Sends local media to Janus/remote. Creates offer sets local desc sends via socket sets remote desc
ReceiverPeer.jsReceives remote media from Janus
SelfServicePeer.jsSelf-service specific peer connection
DeviceHandler.jsMedia device enumeration and management
VolumeMeter.jsAudio level monitoring
iceTest.js / IceTestResult.jsICE connectivity testing for system check
MediaDeviceInfo.jsDevice info abstraction

VideoChat Module (client/features/videochat/)

ComponentPurpose
VideoFeed.js (399 lines)Video element management View: stream management, screenshot capture (5s timeout), speaker output selection, screenshot guide overlay (portrait, id-card, passport), torch/flashlight control, camera facing mode detection
LocalMediaService.jsLocal camera/microphone stream management
Presentation/CertPresentation, ImagePresentation, PDFPresentation (PDF.js), HandlerQueue

System Check Module (client/features/system-check/)

Comprehensive system compatibility checking:

  • SystemCheck.js: Main orchestrator
  • CheckList.js / CheckStep.js: Check step management
  • Individual checks: BrowserCheck, CameraCheck, MicrophoneCheck, SoundCheck, ConnectionCheck, LocationCheck, PermissionCheck, MediaCheck
  • StepBar: Progress indicator

Other Features

FeaturePurpose
DeviceChange/Device switching flow with controller, translations
RequestCallback/Callback request UI
selfservice-session-expiration/Session timeout handling
socket-ping/Socket.IO ping measurement
time-slot-picker/Appointment time slot selection

Client UI System

Per-Page Architecture

Each page is a separate bundle with its own script, style, template, and translations:

client/ui/pages/{page-name}/
  {page-name}.script.js    -- Entry point, bundled by esbuild
  {page-name}.trans.js     -- Translations (EN + HU)
  {page-name}.ui.js        -- View definitions
  {page-name}.style.styl   -- Stylus styles

Pages

PagePurpose
404Not found page
appointmentAppointment booking
appointment-waitingAppointment waiting screen
browser-changeBrowser switch prompt
client-gate-demoClient gate demo page
device-changeDevice change flow
feedbackPost-call feedback
good-byeSession end page
homepageLanding page
kiosk-incompatibleKiosk incompatibility
self-serviceMain self-service page (largest, most complex)
self-service-auth-2-factor2FA during self-service
self-service-client-gateClient gate verification
self-service-consent-pepPEP consent form
self-service-consent-ttnyTTNY consent form (DateHandler, FormHandler, OwnerHandler)
self-service-loginSelf-service login
self-service-preparationPre-service preparation
sentryError reporting (Sentry with CustomTransport)
system-checkSystem compatibility check
ui-kitUI component showcase (dev only)
videochatMain videochat page (Chat, ConfirmDialog, Dialog, LocalUser, Prompt, RemoteUser)
waiting-roomWaiting room with queue position
waiting-room-exitWaiting room exit
we-are-closedClosed hours page
web-sdkWeb SDK integration page

Reusable UI Elements

ElementPurpose
accordionExpandable sections
alertAlert messages
CodeInputCode/OTP input
dropdownDropdown select
FormForm management with validation
HighContrastAccessibility high contrast toggle
ImageSlideShowImage carousel
modalGeneric modal dialog
PageBase page component
PdfViewPDF viewer (pdf.js)
PdfViewModalPDF in modal
RatingListStar rating
ScreenScreen component
SelfServiceSkipStepButtonSkip step button
SelfServiceStepBarSelf-service progress
snackBarToast notifications
StepBarProgress steps

Layout Modes

LayoutUse Case
defaultStandard browser (header/footer)
highcontrastAccessibility
kioskPhysical kiosk terminals (no header/footer)
videochatVideo session
videochat-kioskVideo on kiosk

View State Classes

UI state management via CSS classes:

ActiveState, ClosedState, ClosingState, CollapsedState, DisabledState, EmptyState, ExpandedState, HiddenState, HighlightedState, InactiveState, LoadedState, LoadingState, OnlineState, OpenClosedState, OpeningState, OpenState, TransparencyState, VisibilityState, VisibleState


React Integration

Bridge Pattern

React is not the primary UI framework. It’s integrated via a bridge that connects it to the legacy MVC engine.

react-loader.js (81 lines)

  1. Finds #react-root DOM element
  2. Optionally loads props from data-react-main-component-props attribute
  3. Initializes ReactPageContext singleton with translation dictionary
  4. Creates React 18 root via ReactDOMClient.createRoot()
  5. Renders main component

react-page-context.js (51 lines)

Singleton shared context for React components. Holds translation Dictionary instance. Accessed by hooks.

use-translation.js (11 lines)

React hook that returns dict.t.bind(dict) from page context. Allows React components to use the same translation system as the MVC engine.

React Externals

React and ReactDOM are bundled separately as externals:

  • client/externals/react/react.dev.ext.js / react.prod.ext.js
  • client/externals/react/react-dom.dev.ext.js / react-dom.prod.ext.js

Compiled by bin/script/external.compiler.js (Browserify) separately from page scripts (esbuild).


Build System

Architecture

Three parallel build pipelines:

graph TB
    subgraph "Script Pipeline (esbuild)"
        S1["client/ui/pages/**/*.script.js"] --> S2["web/js/{page}/{page}.script.js"]
        S3["client/ui/layouts/**/*.layout.js"] --> S4["web/js/{layout}/{layout}.layout.js"]
    end

    subgraph "External Pipeline (Browserify)"
        E1["client/externals/react/*.ext.js"] --> E2["web/js/react/*.js"]
    end

    subgraph "Style Pipeline (Stylus)"
        ST1["client/ui/pages/**/*.style.styl"] --> ST2["web/css/{page}/{page}.style.css"]
    end

Script Compilation

File: bin/script/script.compiler.js (64 lines)

  • Uses esbuild (not Browserify)
  • Bundles each *.script.js into web/js/ output
  • Optional Istanbul instrumentation for coverage
  • Optional minification and source maps

Hardcoded Path

sourceRoot: '/workspace/vuer_css' is hardcoded — assumes deployment path.

Style Compilation

File: bin/style/style.compiler.js (75 lines)

  • Stylus with autoprefixer (via @techteamer/poststylus)
  • Injects: breakpoints, deviceSizes, colors as Stylus variables
  • Injects: branding resources and values as Stylus variables
  • Global import: client/ui/styles/global.styl
  • Optional CleanCSS minification

Watch Mode

bin/watch/watch.js:

  • Uses chokidar for file watching
  • LiveReload support on port 11080
  • --restart flag for server auto-restart

Development Commands

yarn dev    # Live reload with automatic file rebuilding
yarn build  # Production build

i18n / Translation System

Dictionary Engine

File: engines/translator/Dictionary.js (265 lines)

FeatureDescription
Module-basedTranslation modules are functions that receive a Dictionary and call dict.define(...)
Composabledict.use(transModule) imports translations from another module
Namespacesdict.create(namespace, transModule) for scoped translations
OverridesConfig-based translation overrides via dictionaryOverrides
Locale fallbackIf current locale missing, tries other supported locales
ParametricTranslation functions accept arguments for interpolation
CachingServer-side dictionaries cached by file path
IsomorphicSame Dictionary class works on both server (Twig templates) and client (JS bundles)

Translation Pattern

Every translatable module has a .trans.js file:

module.exports = function (dict) {
  dict.use(require('../../resources/translations')) // global translations
  dict.define({
    greeting: (name) => ({
      en: `Hello ${name}`,
      hu: `Szia ${name}`
    })
  })
}

Locale Detection (priority order)

  1. req.query.lang (sets cookie)
  2. req.cookies.locale
  3. req.locale (from Accept-Language via locale package)

Bilingual Mandatory

Every string requires both English (en) and Hungarian (hu) translations. Configured via locales.supported and locales.default.


Customization System

Architecture

customization/ is a parallel directory structure mirroring client/ and server/:

graph TB
    Entry["customizations.js (entry point)"]
    Entry --> Hooks["Hook Extensions"]
    Entry --> Overrides["Route Overrides"]
    Entry --> Listeners["13 Listener Modules"]
    Entry --> Content["Template Content Injection"]

    Hooks --> QueueHooks["Queue: EmailValidation, Customer RPC clients"]
    Hooks --> ServiceHooks["Service: PortalClient, CustomCompatibilityService"]
    Hooks --> SocketHooks["Socket: Custom events"]

    Overrides --> Homepage["Homepage -> nusz-landing.endpoint"]
    Overrides --> Routes["/registration, /information, /services, /email-*-validation"]

Customization Capabilities

CapabilityDescription
Route overridesReplace default route handlers entirely
Hook extensionsAdd queue clients, services, socket events, routes
Listener modulesReact to lifecycle events
Translation overridesReplace/extend translations
Template overridesCustom Twig templates
Style overridesBranding-specific Stylus files
Custom contentPage-specific content injection

Listener Modules

ListenerPurpose
webrtc-media-optionsWebRTC media constraint customization
route-system-check-donePost system-check behavior
system-check-stepsCustom system check steps
system-check-required-camera-capabilitiesCamera requirements
css-custom-contentCustom content injection
client-gateClient gate customization
self-service-v2Self-service V2 behavior
webrtc-hd-constraintsHD video constraints
config-socketio-settingsSocket.IO config override
get-calendar-nameCalendar selection logic
route-waiting-room-validateWaiting room validation
route-feedback-validateFeedback validation
home-pageHomepage customization

Branding System

customization/ui/branding/ provides:

  • Colors: colors.styl, colors.kiosk.styl
  • Fonts: font.styl, font.kiosk.styl
  • Loaders: loaders.styl, loaders.kiosk.styl
  • Layout styles: Per-layout branding overrides
  • Page styles: Per-page branding overrides (regular + kiosk variants)
  • Icons: SVG icon symbols via Twig template

NUSZ Integration

The current customization is for “NUSZ” (likely a Hungarian financial institution):

  • Custom landing page, registration, information, services pages
  • Email validation flow
  • Custom submit-registration API
  • Portal client integration

Template Rendering Pipeline

Server-Side: Template.js (69 lines)

The Template class bridges server routes to Twig templates:

  1. Route handler creates a Template instance with template path
  2. Template constructor auto-generates:
    • entryScript: /js/{page}/{page}.script.js?{randomizer}
    • entryStyle: /css/{page}/{page}.style.css?{randomizer}
    • kioskStyle: /css/{page}/{page}.kiosk.style.css?{randomizer}
    • brandingStyle: /branding/pages/{page}.style.branding.css?{randomizer}
    • dictPath: Path to translation module
  3. render(req, res):
    • Sets locale from request
    • Copies browser info
    • Creates socket token (JWT) for the session
    • Gets CSRF token
    • Calls template:custom-content:{pageName} hook for custom data
    • Calls webrtc:hd-constraints hook
    • Gets Socket.IO settings via hook
    • Renders Twig template with all data

Twig Engine

engines/twig/render-server.js sets up Twig with custom filters:

FilterPurpose
documentTypeFilterDocument type formatting
filesizeFilterFile size formatting
formatDateDate formatting
getSrcResource URL generation
timestampFilterTimestamp formatting
zeropadZero-padding numbers

Security Analysis

See security-audit for the full security posture.

Strengths

#Control
1Helmet.js comprehensive HTTP security headers
2CSP with nonces: Per-request nonce for script tags
3CSRF protection: csurf middleware on most routes
4Host header validation: Blocks unexpected Host headers
5JWT socket auth: Short-lived (2-min) tokens for Socket.IO
6JWE encryption: node-jose for sensitive token encryption
7Redis sessions: Server-side session storage
8IP rate limiting: Configurable per-endpoint throttling
9MIME type validation: wasmagic for uploaded file validation
10Sensitive config masking: getSecureConfig() redacts sensitive fields
11Path traversal protection: TransportPool validates session class paths
12OpenAPI validation: Input validation on can-identify and is-compatible APIs
13Sandbox CSP: Restrictive sandbox on most pages

Vulnerabilities / Concerns

HIGH: innerHTML XSS Vectors

Multiple innerHTML assignments with potentially untrusted data:

  1. VideoFeed.js:337this.videoMessageContent.innerHTML = content (verify all callers)
  2. VideoFeed.js:374/378holdMedia from server via socket event. If URL is user-controlled, XSS.
  3. modal.js:168/187 — Modal content injection
  4. Form.js:87/222 — Translation injection (safe if translations trusted)
  5. waiting-room.script.js:37/43 — Video/image source injection
  6. RequestCallback.js:99 — Translation text injection

HIGH: CSRF Bypass

submit-appointment route has CSRF protection commented out (routes.js line 254).

MEDIUM: CORS Configuration Mutation

WebSDK CORS handler mutates shared corsOptions.origin based on Referer header. Under concurrent requests, one request’s origin could leak to another. Race condition.

MEDIUM: Self-Signed Cert Bypass

settings.allowSelfSignedCerts disables TLS verification globally (NODE_TLS_REJECT_UNAUTHORIZED = '0'). Should be dev-only but controlled by config.

MEDIUM: Socket Token Shared Secret

Single jwt.secret for all socket tokens. Compromise allows forging auth tokens.

SeverityIssue
LOWCSP middleware completely skipped for IE and Safari
LOWDev mode adds 'unsafe-eval' to CSP for Istanbul
LOW*.google.com and *.gstatic.com always in CSP
LOWconfig.get() treats 0 and '' as missing values

Code Smells, Hacks & TODOs

See tech-debt for tracking.

TODOs

LocationNote
server.js:158-159customerDocuments and flowDocuments RPC clients: “TODO: remove? Never used…“
feedback.endpoint.js:18”TODO: assert if feedback was already given”
DeviceHandler.js:133Generic “TODO” without description
videochat.services.js:150”TODO: handle when this is called meanwhile a sender peer is trying to reconnect”
videochat.services.js:192”TODO: do something with publishers”
videochat.script.js:330”TODO: needs server side implementation”
self-service.ui.jsMultiple TODOs for “upload task” (lines 163, 238, 525, 587, 733, 783) and “flow action messages” (line 895)
self-service.script.js:16”FIXME why is this needed again?” for initial device summary call

FIXMEs

LocationNote
SelfServiceTransportSession.js:25”FIXME: after socket disconnect this is just noise”
SelfServicePeer.js:51”FIXME log order”
self-service.script.js:16Device summary fetch purpose unclear

Code Smells

IssueDetails
Mixed config accessconfig.deviceChange (direct) vs config.get('deviceChange') (safe)
Commented-out codesocket/client.js has auth imports commented out, Peer.js has entire stat watcher commented out
Inconsistent error handlingSome socket callbacks use cb('error'), others cb(new Error(...)), others cb(err.message)
setInterval without cleanupIpFilterService creates interval in constructor, never cleared
Magic stringsSocket event names duplicated across files with no constants
Error handler signatureWebServer.js error handler missing next parameter
Filename typokiosk-compatiblity.js (missing ‘i’ in compatibility)

Deprecated Patterns

PatternReplacement
pc.addStream(stream) in Peer.jsShould use addTrack()
pc.onaddstream in Peer.jsShould use ontrack
Browserify for externalsesbuild used for main bundles already
csurf packageDeprecated with known issues

Dead Code & Suspicious Patterns

Dead Code

LocationDescription
Peer.js startWatcher()Always returns false, entire implementation commented out (lines 133-210)
server.js:158-159customerDocuments and flowDocuments RPC clients likely unused
socket/client.jsTwo require lines commented out (auth, serviceContainer)
WebServer.js:156log4js.connectLogger commented out

Suspicious Patterns

PatternConcern
window.socketio = this.connectionGlobal window assignment in SocketService.js
IpFilter HTTP 200Rate-limited requests return 200 instead of 429 (intentional stealth)
Radio.deleteChannelCalls this._channels.delete(channel) twice (line 51-52)
Session cookie deletionIn auth.js socket event, delete sessionData.cookie mutates session data object

Data Flow Diagrams

Customer Journey: Homepage to Videochat

sequenceDiagram
    participant B as Browser
    participant E as vuer_css (Express)
    participant S as vuer_css (Socket)
    participant O as vuer_oss (RabbitMQ)
    participant J as Janus

    B->>E: GET /
    E-->>B: HTML + socketToken

    B->>S: Socket.IO connect
    B->>S: auth:auth(token)
    S->>S: verifyToken(jwt) + sessionStore.get()
    S-->>B: auth:ok

    B->>E: GET /system-check
    E-->>B: HTML + socketToken
    Note over B: Runs browser/cam/mic/connection checks
    B->>S: systemCheck:results
    S->>O: queue-customer-history

    B->>E: GET /waiting-room
    E-->>B: HTML + socketToken
    B->>S: join-waiting-room
    S->>O: rpc-waiting-room.join
    S-->>B: queueLength

    Note over B: Waiting...
    O->>S: queue-videochat-css (invite)
    S-->>B: videochat:invite

    B->>E: GET /videochat
    E-->>B: HTML + socketToken
    B->>S: videochat:join
    S->>O: rpc-videochat-oss
    S-->>B: roomData + peerConfig

    B->>S: senderPeer:init
    S->>O: TransportPool -> Janus
    S-->>B: offer
    B->>S: senderPeer:start(jsep)
    S->>O: TransportPool -> Janus
    S-->>B: answer

    Note over B,J: WebRTC media flowing via Janus gateway

Self-Service Flow

sequenceDiagram
    participant B as Browser
    participant CSS as vuer_css
    participant OSS as vuer_oss
    participant CV as vuer_cv

    B->>CSS: GET /self-service/login
    CSS-->>B: Login page
    B->>CSS: POST login auth
    CSS->>OSS: RPC: login auth
    CSS-->>B: Redirect to 2FA

    B->>CSS: GET /self-service/auth-2-factor
    B->>CSS: POST 2FA send
    CSS->>OSS: RPC: send SMS
    B->>CSS: POST 2FA verify
    CSS->>OSS: RPC: verify token
    CSS-->>B: Redirect to prep

    B->>CSS: GET /self-service
    B->>CSS: socket: selfService:start
    CSS->>OSS: RPC: startSelfService
    B->>CSS: socket: peer:init/start/candidate
    Note over B: WebRTC for photo capture

    B->>CSS: socket: screenshot-recognize
    CSS->>OSS: RPC: upload + ML
    OSS->>CV: CV inference
    CV-->>OSS: Recognition results
    OSS-->>CSS: Results
    CSS-->>B: Recognition results

    B->>CSS: socket: getNextPhase
    CSS->>OSS: RPC: getNextPhase
    Note over B: Consent, wrapup, etc.

Project Structure

server.js                                    Entry point
config.js                                    Configuration
server/
  service_container.js                       DI container + event bus
  SocketTokenStorage.js                      JWT token management
  auth.js                                    Socket auth helpers
  diagnostic.js                              Server diagnostics
  logger.js                                  Log4js setup
  bootstrap/connection/rabbitmq.js           RabbitMQ connection
  flow/Flow.trans.js                         Flow translations (EN+HU)
  model/DeviceChangeInfo.js                  Device change model
  queue/
    rpc_client/*.js                          25+ RPC clients
    rpc_server/*.js                          4 RPC servers
    queue_client/*.js                        9 queue clients
    queue_server/*.js                        6 queue servers
  service/*.js                               11+ services
  socket/
    socket-server.js                         Socket.IO setup
    client.js                                Socket client init
    events/*.js                              15 event modules
    models/*.js                              3 data models
  transport/*.js                             Transport layer (5 files)
  util/*.js                                  2 utility modules
  web/
    WebServer.js                             Express setup (553 lines)
    Template.js                              Template rendering
    routes.js                                Route registration (295 lines)
    routes/*.endpoint.js                     30+ route handlers
    api/*.js                                 25+ API handlers
    helper/*.js                              5 helpers
    middleware/*.js                           4 middleware

client/
  engine/                                    Custom MVC framework
    controller/Controller.js                 MVC Controller
    message/Intent.js                        Message intent
    radio/Radio.js, Channel.js               Pub/sub messaging
    service/Service.js, ServiceManager.js    Singleton services
    view/View.js                             Core View (751 lines)
    view/Template.js                         Client template engine (386 lines)
    view/Action.js, AttributeSet.js, etc.    View supporting classes
  features/
    auth.js                                  Client auth
    compatibility.js                         Browser compat
    socket/SocketService.js                  Socket.IO wrapper
    webrtc/*.js                              WebRTC (9 files)
    videochat/*.js                           Videochat (7 files)
    system-check/**/*.js                     System check (20+ files)
    DeviceChange/, RequestCallback/          Feature modules
  react/
    react-loader.js                          React <-> MVC bridge
    react-page-context.js                    Shared context singleton
    hooks/use-translation.js                 Translation hook
  ui/
    elements/**/*.js                         60+ reusable UI elements
    layouts/**/*.js                          5 layouts
    pages/**/*.js                            30+ page types (50+ files)
    regions/**/*.js                          3 regions
    states/*.js                              19 view state classes
    styles/                                  Stylus CSS
  externals/react/*.js                       React bundles (Browserify)
  resources/*.js                             Colors, breakpoints, translations

customization/
  customizations.js                          Entry point (90 lines)
  branding-options.json                      Branding defaults
  listeners/**                               13 listener modules
  resources/translations.js                  Translation overrides
  server/**                                  Server-side overrides
  ui/**                                      UI overrides (styles, templates, scripts)
  flow/**                                    Flow overrides

engines/
  translator/**/*.js                         Translation engine (Dictionary)
  twig/render-server.js                      Twig setup + custom filters
  build/*.js                                 Build utilities
  util/*.js                                  Template filters/utilities

bin/
  build/build.js                             Master build
  script/script.compiler.js                  esbuild compiler
  script/external.compiler.js                Browserify for externals
  style/style.compiler.js                    Stylus compiler
  watch/watch.js                             File watcher + LiveReload
  validate-config.js                         Config validation (AJV)

config/                                      Environment configs (dev.json, docker.json)
web/                                         Compiled static assets (DO NOT edit)
test/tests/unit/                             Jest unit tests

Testing

yarn test:unit                    # Run all unit tests (Jest)
yarn jest test/tests/unit/        # Specific directory
yarn jest sometest.test.js        # Single file
  • Unit tests only (no E2E in this project)
  • Tests organized by: api, client, engines, flow, middleware, queue, routes, services, socket, transport
  • Shared fixtures: /workspace/test_resources

Browser Compatibility

Extensive polyfill support:

  • html5shiv, js-polyfills, core-js
  • regenerator-runtime
  • resize-observer-polyfill