portal_css — Portal Client Side Server

Orientation

This doc lets a future engineer/agent understand and run portal_css. Base branch documented: devel (tip 019f1705), 422 tracked files (recursive, git ls-tree -r --name-only devel). All devel-specific claims below were confirmed via git show devel:<path>; see Sources. Sibling docs: architecture-overview, vuer_oss, customization-architecture, customization-branch-catalog.

The working tree was checked out on customization/instacash (HEAD b0a4a37a), NOT devel. b0a4a37a is the tip of customization/instacash (confirmed: git branch --contains b0a4a37a → only customization/instacash); devel's tip is 019f1705. Everything in this doc was verified against the devel branch via git show devel:<path>. The checked-out customization branch layers in the instacash / MBH integration and carries 684 tracked files (git ls-tree -r --name-only HEAD) — the 262 extra files (e.g. the full customization/ instacash overlay) are not on devel and are flagged inline.

1. Purpose & role in FaceKom

portal_css is the Portal Client Side Server (package.json:2-4"name": "portal_css", "description": "Portal Client Side Server"). It is a customer-facing Node.js web app that renders the FaceKom Portal front end (login, registration, password flows, personal-data, session timeout) and proxies all customer/auth/business operations to other FaceKom services over RabbitMQ RPC. It is a sibling/cousin of the operator-facing CSS; readme.md:5 states it “Keeped the core functionality from CSS”.

Verified feature set (readme.md:4-18):

  • Customer registration; login with credentials (configurable via OSS PortalData attributes); login with JWT token (authenticated by OSS).
  • Customer data cached in Redis with configurable TTL.
  • Direct redirect to CSS (authenticated by CSS); auto-expiring session with a front-end timer.
  • Personal-data change, password change, password recovery, strong customer authentication, access to Open Hours settings, logout.

"CSS" here means Client Side Server, not stylesheets. The repo name portal_css = "portal client side server".

It is heavily customization-driven: the in-tree customization/ overlay on the checked-out worktree branch customization/instacash (HEAD b0a4a37a) is the instacash / MBH integration (customization/branding-options.json:5-8 references mbh_logo*). On devel itself the customization/ tree carries the neutral defaults (git show devel:customization/branding-options.json references facekom/vur logos, not mbh_logo*). Per-tenant variants live on dedicated customization/* and update/customization/* branches (see branch list in customization-branch-catalog).

2. Tech stack & runtime

  • Language/runtime: Node.js, CommonJS. Engine requirement node >=16.14.2 (package.json:6-8). CI (Travis) runs Node 20 (.travis.yml:6-7); changelog notes a “Travis Node 20 update” (FKITDEV-3703, changelog.md:16).
  • Package manager: Yarn with an offline mirror at /workspace/npm-packages-offline-cache and --install.ignore-optional true (.yarnrc). .npmrc sets scripts-prepend-node-path=true. A yarn.lock (~347 KB) is committed; npm scripts are also used (npm run build, npm test).
  • Web framework: Express 4 (package.json:62) + express-session with connect-redis store (server/web/WebServer.js:1-7,259-290).
  • Realtime: socket.io v4 server + client (package.json:96-97; server/socket/socket-server.js:1-15).
  • Templating: Twig server-side (twig dep; engines/twig/render-server.js). (Dustjs deps are present — dustjs-helpers, dustjs-linkedin — and a browserifyDust transform is referenced by the client bundler at bin/script/script.compiler.js:4; note recent commits “Remove Dust” / “replace mmagic with wasmagic”, git log.)
  • Client build: Browserify + Babel (@babel/preset-env, @babel/preset-react) for JS, Stylus + clean-css + autoprefixer/poststylus for CSS (bin/script/script.compiler.js, bin/style/style.compiler.js).
  • Front-end libs: React 18 (loaded as a browserify external, see §3/§6), Bootstrap 5.3.3 + jQuery 3.7.1 (web/libs/...).
  • Messaging client: @techteamer/mq (alpha) — ConnectionPool, RPCClient, RPCServer, QueueClient (package.json:39; server/bootstrap/connection/rabbitmq.js:5).
  • Cache/session store: Redis via redis v4, async-redis, connect-redis v7 (package.json:42,55,90).
  • Config: getconfig v4 + dot-object + optional Spring Cloud Config client (config.js).
  • Logging: log4js with console/file/syslog/papertrail appenders (server/logger.js).
  • Security middleware: helmet, csurf, cors, nocache, custom CSP + IP filter (server/web/WebServer.js, server/web/routes.js, server/service/IpFilterService.js).
  • Image processing deps: heic-convert, png-to-jpeg, wasmagic (server/util/magic.js; package.json:48,86,105).
  • Error reporting: @sentry/browser (client) + a Raven-JS endpoint (server/web/api/sentry.js, web/libs/raven-js/).

3. Build & run

Entry points

  • Main / start: server.js (package.json:5 "main": "server.js"; start script node server.js).
  • server.js boot sequence (server.js:190-200): config.loadedrabbitmq bootstrap → setupQueuesetupServicessetupCustomizationsStartWebServerStartSocketServer.
  • Two listeners, both bound to 127.0.0.1 behind nginx: Web server on port 10383 (server.js:155-156) and Socket.IO server on port 10382 (server.js:173-175). nginx fronts them (nginx_portal_css_docker.conf:1-7).

package.json scripts (verbatim — package.json:9-22)

ScriptCommandWhat it does
buildnode bin/build/build.jsRuns the full client build: externals + scripts + styles in parallel (bin/build/build.js:3-7), then exits unless --watch.
jestjest --config ./test/jest.config.js --runInBandRuns the Jest unit suite serially. Note: no test files committed yet (only test/tests/.gitkeep).
testnpm run lint --silent && echo 'npm test: OK'The test task is lint-only (then prints OK). Used by CI.
linteslint . ./bin/**/* ./bin/* && echo 'npm run lint: OK'ESLint over the repo + bin/.
lint:fixeslint . ./bin/**/* ./bin/* --fixESLint autofix.
startnode server.jsStarts the portal server (web + socket + queue).
stylenode bin/style/style.taskCompile Stylus → CSS only (bin/style/style.task.js).
scriptnode bin/script/script.taskBrowserify+Babel bundle the page/layout scripts only (bin/script/script.task.js).
script:externalnode bin/script/external.taskBundle the React externals only (bin/script/external.task.js).
script:allnode bin/script/external.task && node bin/script/script.taskExternals then page scripts.
watchnode bin/watch/watchBuild externals+scripts+styles in --watch mode with livereload (bin/watch/watch.js).
creditsnode bin/credits > CREDITS.htmlGenerate an HTML license report via license-checker (bin/credits).

Dockerfile(s)

No Dockerfile is tracked in this repo (verified: find -iname 'Dockerfile*' excluding node_modules returns nothing; git ls-tree shows none). The container image is built elsewhere. What is in-repo: supervisor + nginx configs that the image mounts under /workspace/portal_css.

  • supervisor_portal_css_docker.conf runs three programs as user techteamer: nginx (daemon off), redis (/usr/bin/redis-server /etc/redis/redis.conf), and portal_css (directory=/workspace/portal_css, environment=NODE_ENV="docker", command=node server.js, autorestart=true, exitcodes=0,2, stopsignal=TERM).
  • supervisor_portal_css_dev.conf is the dev variant (present, not opened line-by-line).
  • nginx_portal_css_docker.conf: nginx listens :80, root /workspace/portal_css/web, proxies /socket.io127.0.0.1:10382 (upstream socketio-css) and everything else → 127.0.0.1:10383 (upstream portal-css) via try_files $uri @nodejs; long-cache static dirs (/js /css /img /font /libs /icon /audio /entry, expires 1y); client_max_body_size 25M; X-Content-Type-Options nosniff; rewrites upstream 500→400 serving empty error.html, and 502→/502.html (loader). nginx_portal_css_dev.conf is the dev variant.

Local run shape (inferred from configs — see gaps)

The dev/docker model expects: Redis reachable via unix socket /var/run/redis/redis-server.sock (config/dev.json:36-39, config/docker.json:32-35), a RabbitMQ over TLS (amqps://) with client certs under /workspace/vuer_mq_cert/... (config/*.json queue), and host names derived from DEV_DOMAIN//etc/hostname when NODE_ENV=dev|travisci (config.js:42-60). NODE_ENV selects the getconfig file (devconfig/dev.json, dockerconfig/docker.json).

4. Top-level structure (meaningful dirs)

PathRole
server.jsProcess entry: signal handlers, boot pipeline, web+socket listeners (server.js).
config.jsgetconfig wrapper: dev host derivation, ms→seconds coercion, secure-by-default CORS/socket origins, Spring Cloud Config loader, config.get/has/getSecureConfig (config.js).
config/Env config JSON: dev.json, docker.json (getconfig profiles).
server/Back-end: web server, routes, API handlers, services, queue RPC/queue clients & server, socket server, logger. (See §6.)
server/web/Express WebServer.js, routes.js, routes/*.endpoint.js (page renderers), api/* (form/AJAX handlers), middleware/*, helper/*, Template.js.
server/queue/RabbitMQ surface: rpc_client/*, rpc_server/PortalRPCServer.js, queue_client/Customer.js.
server/service/Stateful services: Portal, Branding, Compatibility, Socket, IpFilter, CustomerData.
server/socket/socket.io server (socket-server.js), client.js, events/*, models/UserData.js.
server/bootstrap/connection/rabbitmq.js — builds the @techteamer/mq ConnectionPool, exits process on connection close.
server/logger*log4js setup + custom syslog/papertrail appenders.
client/Front-end source compiled into web/. Subtrees: engine/ (MVC-ish view/service/radio framework), ui/ (pages, layouts, elements, regions, styles, libs/bootstrap), react/ + externals/react/ (React loader/hooks), features/, resources/ (breakpoints, colors, device-sizes, translations), utils/.
customization/Tenant overlay (instacash/MBH on devel): customizations.js (hooks/overrides registry), branding-options.json, listeners/*, server/ overrides, ui/ (branding styles, custom pages/layouts/regions, translations).
engines/Build & render engines: build/ (build/bundle/watch/livereload), twig/render-server.js, translator/ (Dictionary + config), util/* (twig filters, inheritance, getSrc).
bin/First-party CLI build helpers (see §5).
web/Build output + static assets served by nginx: compiled js/,css/,branding/ (generated), plus committed font/,icon/,img/,libs/,entry/, error.html, 502.html, robots.txt.
test/Jest config, custom test sequencer, travis.sh, testconfigs/local.json ({}), coverage dir.
maintenance/, security/Monthly markdown logs (2023-09 … 2026-04) — operational/audit records, not code.
logs/Runtime log dir (logs/.gitkeep); log4js writes logs/server.log (server/logger.js:33).
.github/CODEOWNERS, workflows pr-title-lint.yaml, release-caller.yaml.
.husky/git hooks: commit-msg, pre-commit, prepare-commit-msg (commitlint present, commitlint.config.js).
.agents/Agent-tooling scaffolding dirs (ao/, knowledge/, plans/, …); all empty except .gitignore. Not part of the app.

5. First-party bin/ scripts (each READ)

All paths under bin/. These are the build pipeline; none are runtime server code.

  • bin/build/build.js — Orchestrator for npm run build. Pushes --no-exit so sub-builds don’t exit early, then Promise.all of external.task + script.task + style.task; logs done Build and process.exit() unless --watch (bin/build/build.js:1-12).
  • bin/credits (shebang #!/usr/bin/env node) — Runs license-checker over process.cwd() (production:false) and prints an HTML <table> of name/license/repo/publisher. Used by npm run creditsCREDITS.html (bin/credits:1-24).
  • bin/script/external.task.js — Defines the React externals bundle config (input client/externals/react/react.*.ext.js and react-dom.*.ext.js, output web/js/externals) and calls engines/build/build('externals', …). react-dom is bundled with react marked external to avoid a 2nd React instance. Adds watch globs + livereload when --watch (bin/script/external.task.js).
  • bin/script/external.compiler.js — Factory createCompiler({externalModules, exposeName}) returning a browserify compiler that .external(...).require(path,{expose}) to produce an externally-requirable bundle; honors build.generateSourceMaps (bin/script/external.compiler.js).
  • bin/script/script.task.js — Page/layout JS bundle config: globs client/ui/pages/**/*.script.js, customization/ui/pages/**/*.script.js, customization/ui/layouts/**/*.layout.js, client/ui/layouts/**/*.layout.jsweb/js; in dev|travisci also bundles client/ui/test/*.js; watch globs include .js/.jsx/.dust. Calls build('scripts', …) (bin/script/script.task.js).
  • bin/script/script.compiler.js — The per-file browserify compiler: .external(['react','react-dom']), transform(browserifyDust), babelify with @babel/preset-env + @babel/preset-react (+ optional babel-plugin-istanbul when build.istanbul), plugin(browserify-require-not-found-parent); returns {output, stats.size} (bin/script/script.compiler.js).
  • bin/style/style.task.js — Stylus bundle config: page styles, layout styles, customization/ui/branding/**/*.branding.stylweb/branding, custom pages/layouts → web/css; watch includes customization/branding-options.json. Calls build('styles', …) (bin/style/style.task.js).
  • bin/style/style.compiler.jscompileStylus: imports client/ui/styles/global.styl, defines breakpoints/device-sizes/colors and branding resources+values into Stylus, runs autoprefixer via @techteamer/poststylus, and minifies with clean-css when build.minify; inline sourcemaps when enabled (bin/style/style.compiler.js).
  • bin/watch/watch.js — Pushes --watch to argv then requires external.task, script.task, style.task (4 lines). Backs npm run watch (bin/watch/watch.js:1-4).

Shared build engine they all call: engines/build/build.js (build(name, {watch, bundle, liveReload}) — runs engines/build/bundle.js per config, optional chokidar watch + livereload; instantiates a BrandingService to feed branding into compilers) and engines/build/bundle.js (glob→read→compile→write outputs/sourcemaps under outputRoot).

6. Key modules/services/entities (READ)

Boot & DI

  • server/service_container.js — Exports a singleton { emitter: new ServiceBus() }. ServiceBus extends WildEmitter and adds the hook/override system used everywhere: addHook/removeHook/callHooks/callOnlyHook (parallel/first-only handlers) and registerOverride/callOverride (replace a default block). This is the core of the customization mechanism (server/service_container.js).
  • server/bootstrap/connection/rabbitmq.js — Builds ConnectionPool({defaultConnectionName:'default'}), setupQueueManagers(config.queue), and registers a close handler that process.exit(2) if any RabbitMQ connection drops. Populates serviceContainer.queue/rpcServer/rpcClient/queueClient/... (rabbitmq.js).
  • config.js — getconfig loader with a custom JSON-syntax-error reporter (prints offending file slice, exit(2)); dev host derivation; ms-string→seconds coercion for session/sms timeouts; secure CORS/socket-origin defaults; optional Spring Cloud Config; config.get(path,default), config.has(path), config.getSecureConfig() (redacts security.sensitiveConfigFields).

Web layer

  • server/web/WebServer.js — Builds the Express app. Notable: disables x-powered-by; host-header allowlist (only config.hosts.* accepted, else 400); per-request nonce (uuid); rich req.browser user-agent classification incl. isCompatible/isIncompatible via CompatibilityService; helmet with config-driven options; a per-request CSP builder (nonce script-src, wss://hosts.portal connect-src, route-specific sandbox flags for /loan/application, /lobby, /videochat, /documents, downloads; livereload :11083 allowances; instacash.cspDirectives merge); body-parser (25 MB JSON + csp-report), cookie-parser, locale negotiation. Chainable setup methods: setupLocale → setupRedis → setupSession → setupTwig → setupRoutes → setupCSPReportViolation → setup405Handler → setup404Handler → setupErrorHandler (matches server.js:144-153). Redis session store via connect-redis; iOS-12 SameSite workaround; 404 → 303 redirect to /404 or / for HTML, else 404 (server/web/WebServer.js).
  • server/web/routes.js — Central route table. Mounts customerDataCache, fires middlewares hook, then wires routes gated by config flags and each wrapped in callOverride('routes:<name>', …) so customizations can replace them: / (landing, opt. customerAuthWithToken), /videochat, /404, /login + /api/submit-login (+ ipFilter), password-change/recovery/reset endpoints + APIs, /logout (destroys session), strong-auth /api/submit-token & /api/submit-resend-token (when useStrongAuthentication), /registration (when features.registration), /personal-data (when features.personalDataChange), /ui-kit (when build.uikit), /api/sentry; CORS applied to /api; finally fires the routes hook (server/web/routes.js).
  • server/web/middleware/requireSession.js — Redirects to /login (or 403 for /api/*) if no session.customerId; stashes redirectTo (requireSession.js).
  • server/web/middleware/customerAuthWithToken.js — On / with a ?token, calls rpcClient.jwtAuth.auth(token); on valid sets session.customerId + redirects / (JWT login from OSS) (customerAuthWithToken.js).
  • server/web/api/submit-login.js — Credential login flow: rpcClient.portal.authLoginCredentials, then if strong-auth required sends SMS token (respecting smsTokenResendInterval and PortalService.smsTokenLimitExceeded), verifies token via rpcClient.portal.verifyToken, sets session, IP-filter release on success, fires customer:login hook (submit-login.js). (Representative of the server/web/api/* handlers — see gaps.)
  • server/web/Template.js, engines/twig/render-server.js — Twig rendering; render-server registers filters timestamp,filesize,documentType and function nameOrder (render-server.js).

Services (server/service/)

  • PortalService.js — Strong-auth token orchestration over RPC: sendSmsToken/sendEmailToken (generate via rpcClient.portal.generateToken, deliver via sendSmsTemplate/sendEmailTemplate), sendPasswordRecoveryEmail (find customer, reject external customers via customizations.rpcClient.instaCash.isExternal, generate redirect token, email recovery URL), and Redis-backed per-customer token-count rate limiting (smsTokenLimitExceeded) (PortalService.js).
  • CustomerDataService.js — Redis cache of customer data keyed customer-data:<id> with sliding TTL; cache miss → rpcClient.getCustomer.get(id); invalidate(id) deletes key (called by PortalRPCServer and on data updates) (CustomerDataService.js).
  • BrandingService.js — Loads customization/branding-options.json defaults, overrides resource paths/values from config.branding, verifies branded files exist on disk (fallback otherwise). Used both at runtime (server.js:131) and by the style compiler/build engine (BrandingService.js).
  • IpFilterService.js — In-memory per-IP(+tag) throttling: createIpFilter/createIpMonitorAndFilter/monitorIp/shouldThrottle/releaseIp; driven by security.ipFilter (suppressAfterAttempts, throttlePeriodMs, coolDownPeriodMs); blocked responses return HTTP 200 bodies ERROR_FKIPFT01/02. Self-pruning setInterval (IpFilterService.js).
  • CompatibilityService.js, SocketService.js — browser-rule checks (used by WebServer req.browser) and socket-side service (present; not opened line-by-line — see gaps).

Realtime

  • server/socket/socket-server.jsstart(httpServer)io(httpServer, config.web.socket); setupEvents loads modules client, events/auth, events/pagevisit, fires socket hook, and invokes each module per connection (socket-server.js). Socket tuning in config.*.json web.socket (maxHttpBufferSize 25MB, pingTimeout 30000, allowEIO3 true).

Customization overlay

  • customization/customizations.js — Registers, via the ServiceBus, the instacash integration: extra RPC clients (instacash-rpc → InstaCash, rpc-callback-request → RequestCallback), services (FakeData, InstaCash, PostalCode), a callbackRequest middleware, a large block of loan/application/document/account routes & APIs under /loan*, /documents, /applications, /api/loan/*, /api/request-callback, /api/submit-document, downloads, etc., plus overrides for routes:landing and routes:videochat, and core listeners (customer-registration, customer-password-policy, customer-login, customer-data-change, config-socketio-settings). module.exports = () => Promise is the setupCustomizations invoked in server.js:138 (customizations.js).

7. Configuration

  • Mechanism: getconfig selects a profile JSON by NODE_ENV (devconfig/dev.json, dockerconfig/docker.json; travisci overrides queue to amqp://localhost:5672, config.js:62-65). No AJV/JSON-schema validation found; validation is ad-hoc (config.js syntax reporter + config.get/has).
  • Env vars (verified): NODE_ENV (profile + dev host logic, config.js:42-65; supervisor sets docker), DEV_DOMAIN (config.js:43), TZ is forced to Etc/UTC (server.js:56), NODE_TLS_REJECT_UNAUTHORIZED='0' set when settings.allowSelfSignedCerts (server.js:58-60). Travis sets NODE_ENV=dev, SLACK_ROOM, PATH (.travis.yml:12-22).
  • Key config domains (from config/dev.json / config/docker.json):
    • features.{login,registration,personalDataChange,sessionTimeout} — route gating.
    • settings.*sessionTimeout,sessionTimeoutModalLimit,customerDataCacheTimeout,passwordResetExpiryPeriod,useStrongAuthentication,smsToken* limits/intervals. ms-strings coerced to seconds in config.js:108-123.
    • redis.socket.path = /var/run/redis/redis-server.sock.
    • web.session (cookie name psid, maxAge 3600000, rolling), web.socketioSettings, web.socket, web.trustedProxy (loopback, linklocal, uniquelocal), web.cors.origin (* → narrowed to hosts.portal by config.js:126-128).
    • security.ipFilter, security.sensitiveConfigFields (redaction list — session secret, queue TLS cert/key/ca, jwt.secret, jwt.keystore.*.k), and (consumed by WebServer) optional security.helmet.*, security.csrf.cookie, security.cookie.sameSite.
    • hosts.{portal,css,esign} (auto-filled in dev, config.js:50-59).
    • queue.{url,options(cert/key/ca),rpcTimeoutMs,rpcQueueMaxSize} — RabbitMQ over amqps:// with client certs.
    • jwt.{secret,expiryPeriod}, documentUpload.mimeTypes, callbackRequest.*, browsers.{compatible,incompatible}, locales, logging, branding, build.* (sourceMaps/minify/liveReload/uikit/react.production), timezone, version.
    • instacash.* (e.g. disablePortal, presetReferrerPolicy, cspDirectives) — tenant-specific (config/dev.json:1-4, config/docker.json:1-5, WebServer.js:201).
  • Spring Cloud Config: if config.springCloudConfigServer is set, config.js:155-185 loads remote config via cloud-config-client and merges with dot-object.

Secrets are committed in the config JSON (web.session.secret, jwt.secret are hard-coded literals in config/dev.json:43,182 and config/docker.json:39,158). These are dev/default values; production is expected to override via Cloud Config / deploy-time config (security.sensitiveConfigFields exists precisely to redact them in getSecureConfig). Treat repo values as non-production.

8. Tests

  • Frameworks: Jest (jest, @jest/test-sequencer, eslint-plugin-jest, istanbul-lib-instrument) and ESLint (eslint-config-standard).
  • Jest config: test/jest.config.js — custom testSequencer (test/lib/jest/test.sequencer), roots/rootDir = repo root, ignores /node_modules/,/web/,/yarn-offline-cache/; coverage via v8lcovonly into test/coverage/jest; testTimeout 30000; transform: {} (no transform — expects plain CJS test files).
  • Run: npm run jest (jest --config ./test/jest.config.js --runInBand). The default npm test is lint-only (package.json:12).
  • CI (.travis.ymltest/travis.sh): Node 20 on Ubuntu focal. Steps: npm run build; yarn install --production=false --force --frozen-lockfile; yarn audit (fail only on severity ≥16 = critical); npm test; git diff --exit-code (excluding .yarnrc) to ensure build output is committed/clean; and a guard that no executable files exist outside bin/ (test/travis.sh:1-28). Branch filter regex restricts which branches run (.travis.yml:28).
  • Coverage tooling: sonar-project.properties → SonarCloud (projectKey=portal-css, org techteamer, lcov at test/coverage/lcov.info).
  • Status: No committed test specs (test/tests/.gitkeep, customization/test excluded); test/testconfigs/local.json = {}.

9. Dependencies on other FaceKom services

All cross-service calls go through RabbitMQ (@techteamer/mq). There are no first-party HTTP calls to other services in the code read (HTTP is inbound only, fronted by nginx). DB access is Redis only (session store + caches). Verified queue surface:

RPC clients (request/reply) — registered in server.js:91-97:

Queue nameClientMethods → action (file)
rpc-vuer-portalPortalRPCClientgetLoginFields, authLoginCredentials, findCustomerId, generateRedirectToken, createCustomer, updateCustomerData, currentToken, generateToken, verifyToken, sendEmailTemplate, sendSmsTemplate, clientErrorLog (rpc_client/PortalRPCClient.js)
rpc-customer-portal-dataPortalDataRPCClientgetRegistrationFields(lang, customerId) (PortalDataRPCClient.js)
rpc-jwt-authJwtAuthauth(token){jwtToken} (JwtAuth.js)
rpc-get-customerGetCustomerget(id) (GetCustomer.js)
rpc-openhoursOpenHoursget(callbackRequestDays, openingHourDays, calendarName) (OpenHours.js)
rpc-openhours-calendarsOpenHoursCalendarsgetCalendars(calendarNames, locale) (OpenHoursCalendars.js)
background-room-exportRoomExportexportRoom(roomId, blackoutOperator) (300 s timeout) (RoomExport.js)

RPC server (this service answers) — server.js:80:

  • rpc-portal-vuerPortalRPCServer: handles action invalidateCustomerCacheCustomerDataService.invalidate(customerId) (rpc_server/PortalRPCServer.js).

Queue client (fire-and-forget) — server.js:102:

  • queue-customerCustomer: setConnectionInfo(customerId, ip, locale, userAgent) (queue_client/Customer.js).

Customization (instacash) adds — customization/customizations.js:12-15:

  • instacash-rpc → InstaCashRPCClient, rpc-callback-request → RequestCallbackRPCClient.

Infra deps:

  • RabbitMQ over TLS (amqps://rabbitmq:5671 docker / amqps://localhost:5671 dev), client certs at /workspace/vuer_mq_cert/*. Process exits(2) on connection loss (rabbitmq.js:14-19).
  • Redis via unix socket /var/run/redis/redis-server.sock — session store + customer-data/token caches (config/*.json, WebServer.js, CustomerDataService.js, PortalService.js).
  • The naming (rpc-vuer-portal, rpc-portal-vuer, rpc-customer-*, rpc-get-customer, rpc-jwt-auth, rpc-openhours*) indicates the counterpart is the vuer/OSS stack — see architecture-overview and vuer_oss. (Counterpart server implementations are out of this repo — see gaps.)

10. Verified gotchas

Working tree ≠ devel. Checked out on customization/instacash (HEAD b0a4a37a), NOT devel (tip 019f1705). Use git show devel:<path> to read the core product; the branch adds the instacash/MBH customization/ overlay (684 files vs 422 on devel) that is not on devel. All devel facts here are confirmed against devel.

No Dockerfile in repo. The image is built externally; only supervisor_* and nginx_* configs + web/ static output live here. Don't expect docker build to work from this tree.

npm test does not run tests. It runs lint only (package.json:12). Unit tests are npm run jest, and no test specs are committed yet (test/tests/.gitkeep).

Build output is committed under web/ and CI enforces it. test/travis.sh:19 runs git diff --exit-code after npm run build — if compiled web/js,web/css,web/branding differ from committed output, CI fails. Always npm run build and commit the result.

Hard process exits. Any RabbitMQ connection close → process.exit(2) (rabbitmq.js). getconfig JSON syntax error → exit(2) (config.js:39). uncaughtException/unhandledRejection are logged but the process is supervised (supervisor … autorestart=true, exitcodes=0,2).

Servers bind to 127.0.0.1 only (ports 10383 web / 10382 socket). They are unreachable without the nginx reverse proxy in front (server.js:156,175; nginx_portal_css_docker.conf).

Host-header allowlist is strict. Requests whose Host is not one of config.hosts.{portal,css,esign} get a bare 400 (WebServer.js:30-37). In dev these are auto-derived from DEV_DOMAIN//etc/hostname (config.js); misconfigured hosts → every request 400s.

Committed secrets are dev defaults. web.session.secret and jwt.secret are literals in config/*.json; override in production. getSecureConfig() + security.sensitiveConfigFields exist to redact them in logs/exports.

Customization is hook-driven, not fork-driven (within a branch). customization/customizations.js injects routes/services/middleware via serviceContainer.emitter.addHook(...) and replaces core routes via registerOverride('routes:<name>'), consumed in server/web/routes.js callOverride(...). Per-tenant content still lives on separate customization/* branches.

@techteamer/mq is pinned to alpha (package.json:39) — the messaging layer tracks a pre-release tag.

Strong-auth & rate limits are time-string config. settings.sessionTimeout, smsTokenResendInterval, etc. accept ms-style strings ("15m","15s") and are coerced to seconds at load (config.js:108-123); integers are treated as already-seconds.

Unverified / gaps

  • Surveyed structurally (listed/sampled), not read line-by-line:
    • client/ (the entire front-end source tree — engine/, ui/pages|layouts|elements|regions|styles|libs, react/, resources/, utils/). I enumerated every file via git ls-tree and read the build configs that consume them, but did not open each .script.js/.styl/.twig/.trans.js.
    • Most server/web/api/* handlers (read submit-login.js as representative) and most server/web/routes/*.endpoint.js page renderers (read routes.js which wires them).
    • server/socket/{client.js,events/*,models/UserData.js} and server/service/{CompatibilityService,SocketService}.js (referenced/instantiated; not opened).
    • server/queue/* were all read; server/util/magic.js, server/logger/{syslog,papertrail}.js, server/web/helper/formatOpenHours.js, server/web/middleware/{customerDataCache,Template}.js not opened.
    • engines/translator/*, engines/util/* (referenced from render-server.js; not opened individually).
    • customization/server/** (the instacash route/api/service implementations that customizations.js requires) — directory exists but those *.endpoint.js/service files were not opened; some referenced files (e.g. server/web/middleware/callbackRequest, several server/web/routes/*.endpoint) are required by customizations.js but I did not confirm each exists on disk.
    • web/ static/compiled assets, maintenance/, security/ monthly logs (not analyzed individually).
  • Counterpart services behind the RabbitMQ queues (vuer/OSS, customer/portal-data providers) are out of repo and inferred only from queue names.
  • supervisor_portal_css_dev.conf / nginx_portal_css_dev.conf read only by name (docker variants opened fully).
  • Other branches (customization/cib, unicredit-srb, takarek-mfszh, dunatakarek, demo, etc.) not inspected here — see customization-branch-catalog.

Sources

  • package.json, readme.md, changelog.md, config.js, server.js
  • config/dev.json, config/docker.json
  • bin/build/build.js, bin/credits, bin/script/external.task.js, bin/script/external.compiler.js, bin/script/script.task.js, bin/script/script.compiler.js, bin/style/style.task.js, bin/style/style.compiler.js, bin/watch/watch.js
  • engines/build/build.js, engines/build/bundle.js, engines/twig/render-server.js
  • server/service_container.js, server/bootstrap/connection/rabbitmq.js, server/logger.js
  • server/web/WebServer.js, server/web/routes.js, server/web/api/submit-login.js, server/web/middleware/requireSession.js, server/web/middleware/customerAuthWithToken.js
  • server/service/PortalService.js, server/service/BrandingService.js, server/service/CustomerDataService.js, server/service/IpFilterService.js
  • server/socket/socket-server.js
  • server/queue/rpc_client/{PortalRPCClient,PortalDataRPCClient,JwtAuth,GetCustomer,OpenHours,OpenHoursCalendars,RoomExport}.js, server/queue/rpc_server/PortalRPCServer.js, server/queue/queue_client/Customer.js
  • customization/customizations.js, customization/branding-options.json
  • test/jest.config.js, test/travis.sh, test/testconfigs/local.json
  • .travis.yml, .npmrc, .yarnrc, sonar-project.properties, .github/CODEOWNERS, client/features/readme.md
  • nginx_portal_css_docker.conf, supervisor_portal_css_docker.conf
  • Repo file inventory: git -C portal_css ls-tree -r --name-only devel (422 files); git -C portal_css log --oneline; git -C portal_css branch -a; find -iname 'Dockerfile*' (none)