vuer_oss — FaceKom OSS (Operator Side Server)
The back-office / operator-side server of FaceKom and the system of record for the whole product. It hosts the admin & agent web console (Twig-rendered), owns the PostgreSQL database (Sequelize, 61 models) and the encryption/key infrastructure, runs the WebRTC recording→conversion→archive pipeline, and exposes the bulk of the platform’s business logic to other services over RabbitMQ RPC. The customer-facing counterpart that browsers/kiosks hit is vuer_css; the two share an almost identical codebase shape but boot different process sets.
- Repo:
/Users/levander/coding/facekom/vuer_oss - Base branch:
devel(origin/HEAD -> origin/devel). CIaudit.yamlruns ondevel+customization/*; PR CI on every PR. - Package name / version:
vuer_oss1.9.11(package.json:2-3).devel-line tip seen:521c44a5d7; a recent tagged build commit is165fdb323e 1.9.11.49 - final. - Owner: TechTeamer (
package.json:35,private: true). Remote:git@github.com:TechTeamer/vuer_oss.git. - README title: “FaceKom OSS” (
README.md:1).
The working tree was checked out on
update/customization/instacash-2026-05-27, NOTdevel. Everything in this doc was verified against thedevelbranch viagit show devel:<path>. The checked-out customization branch adds files that are not on devel (e.g.bin/instacash-cli.js,jest.config-unit.js,SYSTEM_CHECK.md) — those are flagged inline.
Heavily customized via Git branches.
develis the core product; deployments ship fromcustomization/<partner>branches (e.g.customization/dap,customization/nusz,customization/instacash) that layer partner code into thecustomization/tree (887 files on the checked-out branch). See customization-architecture and customization-branch-catalog.
1. Purpose & role in FaceKom
OSS is the operator/back-office half of the FaceKom video-identification (KYC) + video-banking platform. From the source:
- It is a multi-process Node.js monolith — one codebase, seven OS processes, selected per
NODE_ENVand started by supervisord (supervisor_vuer_oss_docker.conf). See §3. - The main process (
server.js) builds a giant service container (server/service_container.js) and instantiates ~150 services (server/service/has 138 files) covering: rooms/videochat, self-service flows, customers, crypto/keys, certificates, trusted timestamps, archive/restore, exports, OCR, computer-vision (CV) orchestration, reporting, SAML/AD/TOTP/WebAuthn auth, e-mail/SMS, and a RabbitMQ RPC surface (server.js:29-611). - It owns the database —
server.jsis the only entrypoint that runs migrations/sync on boot (server/bootstrap/connection/db.js:18-37, called withinitDb=trueonly fromserver.js:106). All other processes connect read/write but do not migrate. - It owns the WebRTC recording pipeline: Janus records media →
convert.js(the converter daemon) consumes aqueue-convertqueue, transcodes via FFmpeg, timestamps, and stores it. - It exposes platform capabilities to vuer_css and other services almost entirely over RabbitMQ RPC (
server.jsregisters 32 RPC servers + 18 RPC clients + 16 queue servers = 66 queue endpoints — counted asgetRPCServer/getRPCClient/getQueueServer(...)call sites, lines ~372-581). CSS calls OSS; OSS rarely serves the public internet directly.
Domain entities (Sequelize models, server/db/model/ — 61 .js files) include: customer, room, flow, flowproto, selfServiceRoom, activity, attachment, cert, mediafile, document, faceComparison, faceRecognition, emrtdInfo, recognitionAttempt, customervalidation, emaillog, smslog*, integrationLog, auditlog, encryption, importedCustomer/Room/Flow, openhour*, job, backgroundProcess (git ls-tree devel:server/db/model).
OSS vs CSS division of labour: vuer_css is the public Express+Socket.IO node a remote customer hits (homepage → waiting-room → videochat). OSS is the internal node the operator/admin uses, and it holds the DB + crypto + heavy processing. They talk over RabbitMQ.
2. Tech stack & runtime
| Aspect | Value | Source |
|---|---|---|
| Language | JavaScript (CommonJS, mostly require) + some TypeScript (.ts) and ESM (.mjs) | package.json, tsconfig.json |
| Runtime | Node.js >=22.18.0 | package.json:38-40 (engines.node) |
| CI Node matrix | 22 and 24 (PR build); audit job uses 22 | .github/workflows/pull-request.yaml, audit.yaml |
| Package manager | Yarn (classic; yarn.lock 525 KB, .yarnrc → --install.ignore-optional true) | .yarnrc, yarn.lock |
| Web framework | Express 5 (express@^5.1.0) + helmet, express-session (connect-session-sequelize), body-parser, multer, csurf | package.json, server/web/WebServer.js:1-20 |
| Realtime | Socket.IO 4 (socket.io@^4.8.1 server + client) | package.json, server/socket/socket-server.js |
| Templating | Twig server-side rendering (twig@^3) | package.json, engines/twig/render-server.js |
| Messaging | RabbitMQ via @techteamer/mq@^7.2.0 (ConnectionPool) | server/bootstrap/connection/rabbitmq.js |
| ORM / DB | Sequelize = @techteamer/sequelize@^6.30.1 (forked), sequelize-cli, umzug migrations; drivers pg+pg-hstore (PostgreSQL primary); README documents OracleDB / MySQL (mysql2) / MSSQL (tedious) opt-ins | package.json, README.md, .sequelize-config.js |
| Cache / locks | Redis (redis@^5.8.0), optional — only if HARedis.config set | server/bootstrap/connection/redis.js:7-9 |
| Config | getconfig@^4.1.0 (NODE_ENV-keyed JSON) + dot-object + optional Spring Cloud Config (cloud-config-client) + dotenv | config.js |
| Config validation | AJV (ajv@^8, draft 2020-12) against docs/config/main.json + ~79 schema files | bin/validate-config.js, docs/config/schemas/ |
| Build/bundle | esbuild (client JS) + Stylus→clean-css/autoprefixer (CSS); chokidar watch; livereload (dev) | bin/script/script.compiler.js, bin/style/style.compiler.js, engines/build/ |
| Auth strategies | passport + @node-saml/passport-saml, passport-activedirectory, passport-fido2-webauthn (WebAuthn), passport-totp, passport-local | package.json |
| Crypto/PKI | node-jose, @lapo/asn1js, cbor, @techteamer/cert-utils, @techteamer/timestamp (RFC3161), @techteamer/acl (ACL), AES-256-GCM (server/gcm.js) | package.json |
| Media/docs | FFmpeg (external bin, via FFmpegService), pdf-lib/pdfkit/pdfjs-dist, docx, mjml/juice (e-mail), @techteamer/xlsx, archiver (+ encrypted zip) | package.json, bin/check (calls ffprobe) |
| Cloud / storage | aws-sdk (S3 storage engine), ssh2/SFTP | engines/storage/s3/, package.json |
| Lint / test | ESLint 9 (flat config eslint.config.mjs, eslint-config-standard), Jest 30 (unit), Playwright 1.56 (e2e), c8/nyc coverage | package.json, jest.config.js, playwright.config.ts |
| Reverse proxy | nginx in front of all node processes | nginx_vuer_oss_docker.conf, supervisor confs |
No Dockerfile / docker-compose exists in the repo (searched
develtree + working tree). "docker" here is aNODE_ENVname (config/docker.json,supervisor_vuer_oss_docker.conf), not a container build. Deployment is supervisord + nginx, presumably inside an externally-built image. The README's "inside docker" instructions refer to that runtime.
3. Build & run
Process model (the key mental model)
One codebase → 7 supervised processes (supervisor_vuer_oss_docker.conf), each node <entry>.js with NODE_ENV=docker, plus nginx + a bundled redis:
| Supervisor program | Entry | Role | Listens |
|---|---|---|---|
vuer_oss | server.js | Main web/socket/RPC server; runs DB migrations on boot | web 127.0.0.1:10081 (server.js:645), socket.io :10080 (server.js:662, config web.socket.port) |
vuer_media | media.js | Media/streaming web server (records, screenshots, replay, attachments) | 127.0.0.1:10079 (media.js:133) |
vuer_oss_convert | convert.js | Converter daemon — consumes queue-convert, transcodes recordings | RPC convert-ping only |
vuer_cron | cron.js | Scheduled jobs (archive, cleanup, expiry, reports) | RPC cron-ping |
vuer_background | background.js | Heavy async work: reports, exports, archive, recognition, integrity | RPC background-* |
vuer_oss_storage | storage.js (root) | Storage-engine worker (move media between storages, e.g. S3) | — |
vuer_integration_log | integrationLog.js | Integration-log RPC sink (encrypted audit of external comms) | RPC integration-log |
All five long-running daemons share the same boot recipe: process-settings → process-listeners → config.loaded → setupServices → connect db → connect redis → connect rabbitmq → initServices → setupQueue → customization() → start (e.g. server.js:104-119, media.js:151-162, cron.js, background.js:257-262, convert.js:81-93).
nginx routing (nginx_vuer_oss_docker.conf): root /workspace/vuer_oss/web; static /js /css /img … served directly with 1y cache; /socket.io → :10080; media paths (records/, selfservice-records/, api/screenshot/, api/emrtd-photo/, …) → :10079; everything else → :10081.
package.json “scripts” — every entry, verbatim
| Script | Command | What it does |
|---|---|---|
cron | node cron.js | Runs the cron daemon process directly. |
convert | node convert.js | Runs the converter daemon process directly. |
style | node bin/style/style.task | Compiles all Stylus → CSS bundles into web/css (resolves bin/style/style.task.js). |
script | node bin/script/script.task | Compiles/bundles all client JS (esbuild) into web/js (resolves bin/script/script.task.js). |
watch | node bin/watch/watch | Watch+rebuild JS, CSS and server files on change (no restart). |
dev | node bin/watch/watch --restart | Dev server with live reload and auto-restart of the node process on server-file change (README’s yarn dev). |
build | node bin/build/build.js | One-shot full build: runs script.task + style.task in parallel, then exits (bin/build/build.js). |
lint | eslint . --max-warnings 0 --ignore-pattern "test/*" && echo 'yarn eslint --max-warnings 0' | Lints everything except test/, zero-warning gate (the CI lint step). |
lint:fix | eslint . --fix && eslint --max-warnings 0 . | Auto-fixes then re-checks with zero-warning gate. |
jest | yarn node --experimental-vm-modules $(yarn bin jest) --config ./jest.config.js | Runs Jest with ESM VM modules and the repo jest config. |
test:unit | yarn node --experimental-vm-modules $(yarn bin jest) unit/* | Runs unit tests (path filter unit/* → test/tests/unit/**). |
test:e2e | yarn playwright test | Runs the Playwright e2e/regression suite. |
credits | node bin/credits > CREDITS.html | Generates an HTML third-party license report (license-checker). |
cov_cleaner | rm -rf coverage/* && rm -rf coverage*/* && rm -rf .nyc_output/* | Clears coverage output dirs. |
validate_config:dev | node bin/validate-config.js config/dev.json | AJV-validates config/dev.json against the schemas. |
validate_config:docker | node bin/validate-config.js config/docker.json | AJV-validates config/docker.json against the schemas. |
main:server.js(package.json:4). There is nobinfield; CLIs live underbin/and are run directly (most are#!/usr/bin/env nodeexecutables).- Start (production): supervisord launches
node server.jsetc. (see table). Start (dev):yarn dev; build assets first withyarn build(README).
Build pipeline internals
bin/build/build.jspushes--no-exit, thenPromise.all([script.task, style.task]).bin/script/script.task.jsdefines esbuild bundles forclient/ui/pages|layouts/**andcustomization/ui/pages|layouts/**→web/js; optional istanbul instrumentation whenbuild.istanbul; minify/sourcemaps gated by config (bin/script/script.compiler.js).sourceRootis hardcoded/workspace/vuer_oss.bin/style/style.task.jscompiles*.style.styl/*.layout.stylfrom client + customization →web/cssvia Stylus, injectingbreakpoints,device-sizes.json,colorsfromclient/resources/,autoprefixer, optionalclean-cssminify (bin/style/style.compiler.js).bin/watch/watch.jssimply pushes--watchand requires script+style+server tasks;bin/server/server.task.jswatches*.twig,*.trans.js,server/**,config/*.json,db/**,engines/**, root entrypoints and (with--restart) restarts the process (engines/build/restart).- Generic builder/bundler/watcher engine:
engines/build/{build,bundle,watch,restart,livereload}.js.
4. Top-level structure (meaningful dirs; vendored excluded)
git ls-tree devel root + sampled. (Excluded: node_modules, .git, logs, tempPath, IDE dirs.)
| Path | What it is |
|---|---|
server.js | Main process entry (42 KB): service container + web/socket/RPC bootstrap. |
media.js | Media web-server process (:10079). |
convert.js | Converter daemon process (consumes queue-convert). |
cron.js | Cron daemon process. |
background.js | Background-worker daemon process. |
storage.js | Storage-engine worker process (root file; distinct from bin/storage.js CLI). |
integrationLog.js | Integration-log RPC sink process. |
config.js | Config loader wrapper around getconfig (+ Spring Cloud, .get/.has, secret redaction). See §7. |
bin/ | First-party CLI/maintenance scripts (see §5). |
server/ | All backend code (services, db, queue, web, cv, ocr, crypto, …). See §6. |
client/ | Browser frontend source (1059 files): ui/ pages+layouts+styles, engine/, features/, services/, resources/, utils/. Bundled into web/. |
web/ | Build output + static assets (js, css, img, font, fonts served by nginx). |
engines/ | Reusable build/storage/translator/twig/worker engines (34 files; see §6). |
db/ | Sequelize migrations: db/migrate/ (127 files) + db/beforeMigrate/. Models live in server/db/model/. |
config/ | Env configs: dev.json, docker.json, roles.json (ACL rules). |
docs/ | Markdown docs + docs/config/ JSON-Schema config spec (main.json + 79 schemas). |
customization/ | Per-partner override tree (hooks/overrides/ui/services/db/translations…). Loaded last in every boot (customization()). |
maintenance/ | Dated maintenance changelog notes (YYYY-MM-DD.md), bundled into PDF via docs.json. |
security/ | Dated security changelog notes (YYYY-MM-DD.md), bundled into PDF via docs.json. |
test/ | Jest + Playwright tests, mocks, seeds, test configs (21,939 files incl. fixtures). See §8. |
workers/ | Web/inline worker source (1 file). |
*.conf | nginx + supervisor configs for dev / docker / e2e. |
changelog.md | Product changelog (75 KB). |
registration-form.yml (91 KB), eslint.config.mjs, babel.config.js, tsconfig.json, .sequelizerc, jest.config.js, playwright.config.ts, sonar-project.properties | Root config files. |
5. First-party bin/ helper scripts (every script, READ)
All paths under bin/ (git ls-tree -r devel:bin). Pattern for nearly all of them: shebang #!/usr/bin/env node → require('../server/bootstrap/process-settings')() + process-listeners → commander arg parsing → wire a subset of services into serviceContainer → setupDb() (Sequelize authenticate + models + helpers) → run. They are operator/ops tools, not part of the request path.
Build pipeline (subdirs — invoked by npm scripts, not standalone ops)
bin/build/build.js— one-shot full build (script + style). §3.bin/script/script.task.js,bin/script/script.compiler.js,bin/script/inlineWorkerPluign.js,bin/script/browserify-istanbul.js— esbuild client-JS bundling (+ istanbul/inline-worker plugins).bin/style/style.task.js,bin/style/style.compiler.js— Stylus→CSS bundling.bin/watch/watch.js— watch JS+CSS+server.bin/server/server.task.js— watch/restart server files.
Top-level ops scripts
bin/archive— archive or restore rooms / flows (--restore); supports single id, all, or a file-of-ids list. Wires Archive/RoomArchive/FlowArchive/AttachmentArchive + crypto services.bin/batch_restore.js— restore all archived rooms / flows / self-service rooms (reconciles archive-folder ids vs DBstatus:'archived').bin/attachment.js—exports <path>dumps customer screenshots to filesystem, orcopysends screenshots to e-sign.bin/check— scans converted rooms; usesffprobeto flag suspicious video durations vs expected (from activity log), optionally--convertto force re-convert viabin/convert.bin/clean-rooms— list (or--delete) leftover-video./-audio.files in the replay dir.bin/comptest.js— connectivity + diagnostics CLI (TCP/Redis/TLS probes, then RabbitMQ-RPC diagnostics from the running server). Flags--conn-only,--json,--verbose. (Documented bySYSTEM_CHECK.md— working-tree only.)bin/convert— (re)convert one or many room ids by pushing to the convert queue;--self-service,-f/--force. Decrypts room files thenqueueClient.convert.convert(...).bin/create-decrypted-room-copy.js— copy + decrypt a room’s streamed/converted videos to/tmp/vuer_oss/(--self-service).bin/create-turn-static-auth-secret.js— print TURN static-auth credentials viaTurnPasswordService(<name> [validitySec] [turnServer] [secret]).bin/create_rab_token.js— generate an encrypted JWT “rab” access token for an<accessPath> <expiry>(usesTokenService,config.jwt.secret).bin/credits— emit HTML third-party license table (license-checker); used byyarn credits.bin/crypt-room— encrypt/decrypt all files of a<roomId>(AES-256-GCM);--decryptto reverse.bin/customer-key— dev-only (exits unlessNODE_ENV=dev): insert/remove a customer’s encryption key (--insert/--memory/--remove).bin/data-export.js— export rooms / self-service rooms / customers by id-lists/types to an output dir; pulls portal data + custom archive (usescustomization/portal/PortalData, setsNODE_TLS_REJECT_UNAUTHORIZED=0).bin/export_batch.js— batched room / self-service / raw-video export with--offset/--limit, per-zip--passwordand--encryption aes256|zip20.bin/export_customers.js— export customers (and timestamp/mediafiles xlsx) with offset/limit/file-of-ids,--dry-run,--stats(uses@techteamer/xlsx,PortalData).bin/flow-editor.js— “Flow Editor CLI” (flow definitions).bin/flow-extractor-portable.mjs— ESM script to query/extract self-service room flows by name/date/status to a file (Sequelize + archive + crypto).bin/import-legacy-data.js— import legacy data from a DB configured underimportData.db(ImportService).bin/integration-log.js— query/export integration-log entries with rich filters (--event/--direction/--category/--channel/--status/--from-date/--to-date/--output-dir).bin/migrate-folder-hierarchy.js— move room records/replay fromroomId/filesto0-99REC-REP/roomId/files(interactive confirm; per-room or all).bin/migrate-rooms— createMediaFilemodels (+ optional--timestamp) for every closed/converted room.bin/migrate-self-service-task-data— migrate self-service v2 steptask.data.<field>intoselfServiceRoom.data.steps[...](--force;<stepType>.<fieldName>args).bin/remove-empty-records-directories.js— remove empty dirs underconvert.recordDirectory/convert.replayDirectory(--skip-record,--skip-replay,--dry-run).bin/self_service_settings_db_checker— get/setfaceComparisonthresholds in the Settings table (--perfect,--match,--write).bin/sms-logs-cli.mjs— query SMS logs by phone numbers, produce a CSV report (--interactive/--phoneNos/--output).bin/storage.js— move media files between storages (--move,--type,--storage,--ids,--all); the CLI behind thevuer_oss_storageworker’s manual operations.bin/timestamp-check.js— verify (or re---verify) RFC3161 timestamp tokens of a--roomId/--selfServiceRoomId.bin/validate-config.js— ESM: AJV-validate a config JSON (argv[2]) against core (docs/config/main.json) + custom (customization/docs/config/custom.json) schema dirs. Backsvalidate_config:*.
bin/config.getter.ts / bin/doc.gen.ts (TypeScript, Deno-or-Node)
bin/config.getter.ts— scans source forconfig.get('a.b.c', default)calls (regex) to enumerate every config key in use. Shebang runs via Deno, falls back tonode --experimental-strip-types.bin/doc.gen.ts— unifiesdocs/config/main.json+ referenced schemas into config documentation. Same Deno/Node shebang.
bin/db/ — database admin
bin/db/create_user— create a user<role> <username> <password> [email] [firstName] [lastName] [phone](UserService/PasswordService/ACL).bin/db/create_user_batch— create many users from a batch file.bin/db/enable_user— re-enable a disabled user by username.bin/db/create_standard_days— seed the 7OpenHourStandardrows (Mon–Fri 10:00–16:00 open, weekend closed).bin/db/sync—sequelize.sync()schema sync;-f/--force(drop+recreate),--noninteractive. Called automatically at boot whendb.syncOnStart(bootstrap/connection/db.js:24).bin/db/truncate— truncate all models (interactive confirm) except an excluded set, in a transaction.bin/db/migrate-data— Umzug-based data migration between DBs (usesdb/beforeMigratestorage).bin/db/migrate-rdbms.js— migrate between two RDBMS URLs--origin/--destination(--testConnectionOnly); runssequelize db:migratewith.sequelizercBeforethen default config on both.bin/db/customer-index.js— re-index all customers (re-saves eachCustomerto refresh search hashes; serial async queue).bin/db/pg_sanity_check— gather PostgreSQL stats (tables, sizes, row counts, relations) as JSON to stdout.
bin/crypto/ — bulk encryption migration
bin/crypto/migrate— after enabling encryption in config, encrypt existing data; optional part arg (emaillog/smslog/customervalidation/customerhistory/callbackrequest/recognitionattempt) or everything. Usesbin/crypto/helpers/customer.js.bin/crypto/decrypt— one-off decrypt of<key> [aad] <data>viaserver/gcm(debug helper).bin/crypto/helpers/*.js— per-entity encryption helpers (activity,attachment,callbackrequest,customer,customerDataChange,customerHistory,customervalidation,emaillog,emrtdinfo,general,recognitionattempt,room,smslog).
bin/instacash-cli.jsexists in the working tree but NOT ondevel(git cat-file -e devel:bin/instacash-cli.js→ fatal). It is acustomization/instacashaddition.
6. Key modules / services / entities (server/ — 679 files)
Immediate children of server/ (git ls-tree devel:server) with verified counts:
| Path | Files | What it is |
|---|---|---|
server/service/ | 138 | The business-logic layer (one class per concern). Key ones below. |
server/web/ | 179 | Express app + 70+ *.endpoint.js admin/agent routes + api/ REST + middleware. |
server/queue/ | 92 | RabbitMQ layer: rpc_server/, rpc_client/, queue_server/, queue_client/, publisher/, subscriber/. |
server/db/ | 66 | sequelize.js (connection), models.js, model/ (61 models), helpers. |
server/cv/ | 42 | Computer-vision orchestration — talks to vuer_cv over WebSocket. |
server/socket/ | 21 | Socket.IO server + event handlers (operator console realtime). |
server/util/ | 18 | Shared utilities. |
server/ocr/ | 15 | OCR recognition client/handlers. |
server/model/ | 12 | Non-DB domain models. |
server/backgroundProcess/ | 10 | Long-running background process framework. |
server/cron/ | 20 | Cron job classes (Archive, Cleanup, DeleteExpiredURL, …). |
server/transport/ | 7 | Transport/signalling helpers. |
server/portal/ | 7 | Portal integration. |
server/flow/ | 6 | Flow engine (FlowService). |
server/bootstrap/ | 5 | process-settings, process-listeners, connection/{db,redis,rabbitmq}.js. |
server/listeners/ | 4 | Event listeners. |
server/{e-mail,sms,logger,faceRecognition,user,room-inspector}/ | 3 each | Email (EmailService, MJML), SMS, log4js logger, face-recognition, user, room inspector. |
server/{errors,convert}/, server/{partner,webrtc}/ | 2/4, 1/1 | Error types; convert pipeline (convert_check); partner; webrtc. |
Singletons at server/ root: service_container.js (the DI/event bus), acl.js (@techteamer/acl), auditlog.js, auth.js, gcm.js (AES-256-GCM), diagnostic.js, SocketTokenStorage.js, logger.js, child_process_promise.js, taskCompatibility.js.
server/service_container.js — the backbone
Exports { emitter: new ServiceBus() }, where ServiceBus extends WildEmitter and adds a hook + override registry: addHook/callHooks/callOnlyHook/hasHook (used so customization() can inject behaviour at named lifecycle tags like 'services', 'queue') and registerOverride/callOverride (swap default implementations). Every process mutates this single shared object (serviceContainer.service.*, .dbModels, .queue, .rpcServer, …).
Representative server/service/ classes (verified present)
RoomService, VideoChatService, WaitingRoomService/DurableWaitingRoomService, SelfServiceRoomService/SelfServiceV2Service/SelfServiceCheckerService, JanusService (WebRTC/Janus control), RecognitionService/FaceRecognitionService, FlowService/FlowLiveUpdateService/FlowFilterService, CryptoServices/ (CryptoService, MediaCryptoService, AttachmentCryptoService, DataCryptoService, CertCryptoService, TranslationCryptoService), CustomerKeyStorageService, CertService, TrustedTimestampService/TrustedTimestampRoomService, StorageService, Export*Service (room/self-service/imported/raw-video/screenshots), TokenService, JWT/JWE TokenStrategyService, TurnPasswordService, SettingsService, IntegrationLogService, ArchiveService + ArchiveServices/.
server/web/ — operator console
WebServer.jsbuilds the Express app:helmet,trust proxy, host-header allow-list (rejects unknownHost), per-request CSP nonce, Twig views (app.set('views', cwd)), sessions persisted in Sequelize (connect-session-sequelize),@techteamer/aclenforcement (WebServerAuth.js).server/web/routes/*.endpoint.js(70+) = the admin/agent UI pages:customerlist,customerProfile,roomlist,room,videochat,flow*,archive/restore,auditlog,reports*,dashboard,users/newuser/edituser,systemsettings,selfservice*,importData*,webAuthn/totp/twoFactorAuth,compat-test,room-inspect, etc.server/web/api/{admin,operator,supervisor,common,import-data}/= role-scoped REST API. OpenAPI:server/web/api-doc/room-bulk-delete.yaml(express-openapi-validator+swagger-parserare deps).
server/cv/ — vuer_cv integration
CVApi.js, CVRecipe/CVTask/CVDetection/CVInstruction/CVOutput/CVResultSummary, VuerCVSession.js, VuerCVWebSocket.js, VuerCVListenerSession.js, VuerCVPADService.js + internals/, tasks/, visions/. This is the client side of the CV/biometrics service vuer_cv (presentation-attack detection, document/face vision tasks) over WebSocket.
engines/ — reusable engines (34 files)
build/— genericbuild/bundle/watch/restart/livereload(drives the npm build scripts).storage/— pluggable media storage:Engine.js,StorageTypeHandler.js,default/(filesystem handlers for room/self-service/attachment),s3/(AWS S3 handlers +s3.js).translator/— i18nDictionary.js+ config (browser + server).twig/render-server.js— server-side Twig rendering.worker/—Runner.js/Worker.jsworker abstraction.util/— Twig filters (formatDate, formatDuration, fileSizeFilter, timestampFilter, zeropad, …) +inheritance.js.
7. Configuration
- Loader:
config.jswrapsgetconfig— it readsconfig/<NODE_ENV>.json(e.g.config/dev.json,config/docker.json). It adds a customfiletype (inline-reads a file path into the value), pretty-printsgetconfigJSON syntax errors with surrounding context andprocess.exit(2), and applies env-specific tweaks (dev hostnames from/etc/hostname/DEV_DOMAIN,travisciAMQP override, derivehosts.cvsfromhosts.cv). - Spring Cloud Config (optional): if
config.springCloudConfigServeris set,config.loadedresolves only after pulling remote config viacloud-config-client(withpromise-retry) and merging it withdot-object. Otherwiseconfig.loaded = Promise.resolve(). All entrypoints awaitconfig.loadedbefore booting — so remote config is a hard boot dependency when enabled. - Accessors:
config.get('a.b.c', default)(safe path lookup) andconfig.has(...)are attached to the config object and used pervasively across the codebase. - Security helpers:
config.getSecureConfig()deep-clones and redacts keys listed insecurity.sensitiveConfigFields(supportsa.*.bwildcards) →'SECRET';config.traceConfig()flattens config for logging. CORS hardening:web.cors.origin === '*'is rewritten tohosts.oss; same for socket.io origins. - DB CA / retry: a
db.options.dialectOptions.ssl.capath under/workspace/is read from file;db.options.retry.matchstrings are compiled toRegExp. - Version stamping: if
.envhasVUER_BUILD_NUMBER, it’s appended toconfig.version(so1.9.11→1.9.11.<n>).
Config schema (AJV)
docs/config/main.json($id: docs/config/main.schema.json, draft 2020-12, “Vuer OSS Config”) references 79 files underdocs/config/schemas/— one per top-level config section:db,queue,cv,webrtc*,convert,crypto,certService/certManager,email,esign,faceRecognition,flow,hosts,jwt,jwe/jwtTokenStrategy,saml,activeDirectory,kiosk,ocr,reporting,integrationLog,integrityCheck,sanctionList,roomBulkDelete,features,logging,HARedis, …additionalProperties$refs the customization schema (customization/docs/config/custom.json).- Config files declare
"$schema": "../docs/config/main.json"(config/dev.json:2) for editor validation;bin/validate-config.jsenforces it at CLI time. config/roles.jsonis the master ACL rule catalogue (alphabetical list of every permission string, e.g.archive.view,auditLog.view,cert.upload.any, …) consumed by@techteamer/acl.
Env vars (verified in code)
NODE_ENV (selects config file; dev/docker/travisci special-cased), DEV_DOMAIN/CI_DOMAIN, TZ (forced to Etc/UTC in process-settings.js), NODE_TLS_REJECT_UNAUTHORIZED (set to 0 when settings.allowSelfSignedCerts or in some export scripts), VUER_BUILD_NUMBER (from .env), DO_NOT_GENERATE_ENCRYPTION_KEY (set by bin/db/migrate-rdbms.js). Sequelize config (.sequelize-config.js) requires exactly one of db.database or db.url, and an explicit db.options.dialect.
config/dev.jsonandconfig/docker.jsoncontain committed example secrets (session secret, TURN credentials, SMTP pass,dbcreds). These are template/dev values; real deployments override via partner config and/or Spring Cloud. Do not treat them as live credentials, and do not copy them into production.
8. Tests
- Frameworks: Jest 30 (unit) + Playwright 1.56 (e2e/regression). Coverage via
coverageProvider: 'v8'→test/coverage/jest(jest.config.js). - Jest config (
jest.config.js): roots at repo root; ignores/node_modules/,/web/,/e2e/; transforms*.tswithbabel-jest; mocksservice_containerwithtest/__mocks__/service_container.js(moduleNameMapper); 10 s default timeout. - Location:
test/tests/→unit/,e2e/,integration/,regression/,playwright/,support/, plus setup files (auth.setup.ts,open-hours.setup.ts).test/{__mocks__,seed,files,lib,scripts,testconfigs,customization,web}/. Unit tests live undertest/tests/unit/**mirroringserver/(api, cron, cv, db, flow, model, ocr, services, socket, …). - Counts (devel):
test/tests/unit/**≈ 349*.test.{js,ts}files;test/tests/**≈ 396*.spec/*.test.{ts,js}overall. - How to run:
- Unit:
yarn test:unit(oryarn jest unit/, or one suiteyarn jest <file>). - e2e:
yarn test:e2e(=yarn playwright test); install browsers first (yarn playwright install --with-depsin docker, elsenpx playwright install); requiresyarn buildon OSS + CSS and copyingtest/testconfigs/test-config-ci.json→config/local.json(README “Running e2e tests”). - Test resources: clone
test_resourcesinto/workspace/test_resources(README).
- Unit:
- Playwright (
playwright.config.ts):workers:1, fully parallel within a worker, fake camera/mic +--disable-web-security, defaultstorageStatetest/tests/playwright/.auth/user.json, locale fromconfig.locales.default,globalTeardownpresent (README warns it can erase the DB). - CI (
.github/workflows/pull-request.yaml): on every PR, Node 22 & 24 →yarn install --frozen-lockfile→yarn lint→yarn build. (No automated test job in CI ondevel/PR.)audit.yaml(push todevel/customization/*):yarn audit+improved-yarn-audit --min-severity critical(with excludes) + an executable-file location check (fails if executables appear outsidebin/,.yarn/,node_modules/, a few test/customization dirs).
e2e tests can wipe your database — the Playwright
globalTeardowntears down DB state. The README's escape hatch is to removeglobalTeardownfromplaywright.config. Run e2e only against a throwaway DB.
jest.config-unit.jsexists in the working tree (usests-jest) but is not ondevel— it's a customization-branch addition. The committedtest:unitscript usesjest.config.js.
9. Dependencies on other FaceKom services (evidenced in code)
RabbitMQ (primary integration bus — @techteamer/mq ConnectionPool)
server/bootstrap/connection/rabbitmq.js builds the pool from config.queue, wires serviceContainer.queue, and process.exit(2) on connection close. RPC servers/clients are registered per process:
server.jsexposes (RPC servers,server.js:347-471):rpc-create-customer,rpc-customer-portal-data,rpc-jwt-auth,rpc-device-change,rpc-get-customer,rpc-waiting-room,rpc-videochat-oss,rpc-callback-request,rpc-openhours,rpc-openhours-calendars,rpc-partner-service,rpc-custom-content,rpc-selfservice-actions,rpc-selfservice-upload,rpc-selfservice-v2,rpc-vuer-portal,client-gate,rpc-system-check,rpc-jwt-kiosk-auth,rpc-kiosk-alive-check,rpc-media-content,rpc-identification-router,rpc-device-compatibility-check,background,rpc-appointment,rpc-document-upload/-delete,rpc-customer-documents,rpc-flow-documents,rpc-presentation, …server.jsconsumes (RPC clients,server.js:426-477+):Ping,SystemOss,Reports,Archive,CronManager,KioskDocumentScan,Portal(rpc-portal-vuer),IntegrationLog,Recognition,EsignApi(rpc-esign:external),XmlReport, … — i.e. it calls cron, background, portal, recognition, e-sign (esign_oss), integration-log services.background.js: serversbackground-ping/-reports/-archive/-room-export/-self-service-room-export/-recognition,rpc-xml-report,rpc-xlsx-report; clientrpc-esign:external(optional dedicated esign queue connection).cron.js: serverscron-ping,cronManager(iffeatures.cronManager); clientrpc-xml-report; queue clientsqueue-storage,queue-selfservice-cron,queue-room-cron.convert.js: queue serverqueue-convert(consumes conversion jobs),convert-ping; pub/subsettings-change.media.js:media-ping.integrationLog.js: serverintegration-log; publishersettings-change.
Database
PostgreSQL (primary, pg/pg-hstore) via forked Sequelize; server.js runs migrations on boot (db.syncOnStart → sequelize db:migrate with .sequelizercBefore then default, plus bin/db/sync). README documents Oracle (12g) / MySQL / MSSQL alternatives. .sequelize-config.js reads db.{database|url, username, password, options.dialect} from config.
Redis (optional)
server/bootstrap/connection/redis.js connects only if HARedis.config is set (“HA Redis” — high-availability shared state/locks), with a reconnect strategy and a PING/PONG health check; failure throws and aborts boot.
Janus / WebRTC + TURN
JanusService controls Janus media servers (config.webrtc.janusServers/janusConfig); TurnPasswordService mints TURN credentials. Recordings land in webrtc.server.recordDirectory/convert.replayDirectory. bin/comptest.js probes Janus + CV + CSS hosts directly.
vuer_cv (computer vision / biometrics)
server/cv/ connects to CV servers (config.hosts.cvs) over WebSocket (VuerCVWebSocket.js) for face/document/PAD tasks. See vuer_cv.
External integrations
SMTP (nodemailer + MJML), SMS (server/sms/), SFTP/SSH (ssh2, SFTPService in convert.js), e-sign (rpc-esign:external → esign_oss), Google Play Integrity / Apple integrity (@googleapis/playintegrity), SAML/AD IdPs, sanction-list checks, S3 (aws-sdk).
10. Verified gotchas
Working tree ≠ devel. Checked out on
update/customization/instacash-2026-05-27. Usegit show devel:<path>to read the core product; the branch adds non-devel files. All facts here are fromdevel.
Multi-process, one codebase.
node server.jsis not the whole app — you must also runmedia.js,convert.js,cron.js,background.js,storage.js,integrationLog.js(supervisord does this). Running onlyserver.jsleaves media, conversion, cron, exports and integration-log dead.
Only
server.jsmigrates the DB.bootstrap/connection/db.jsruns migrations/sync only when called withinitDb=true, which happens solely fromserver.js:106. Start the others against an un-migrated DB and they'll fail. Withdb.syncOnStarttrue, boot mutates schema (incl.bin/db/sync).
RabbitMQ is mandatory & fail-fast. A closed AMQP connection triggers
process.exit(2)(rabbitmq.js). No broker → no boot. Mostbin/ops scripts also open the queue pool.
Hardcoded ports & paths. Web
:10081, socket:10080(configurable), media:10079are literals inserver.js/media.js; many compilers/configs assume the absolute workdir/workspace/vuer_oss(script.compiler.jssourceRoot, nginxroot, db CA path gate). Deploy there or expect breakage.
No Dockerfile in-repo. "docker" = a
NODE_ENV/supervisor profile, not a container build. Runtime = supervisord + nginx (+ bundled redis) in an externally-built image.
Committed example secrets in
config/dev.json/config/docker.json(session secret, TURN/SMTP creds). Override in real deployments; never promote to prod.
Spring Cloud Config gates boot. If
springCloudConfigServeris configured, every process blocks onconfig.loadeduntil the remote config server answers (with retry) — a remote outage stalls startup.
Customization is load-order-sensitive. Each entry requires its
customization/*-customization.jsbeforesetupServicesso hooks register in time (comment incron.js:16), and callscustomization()last in the boot chain. The hook/override mechanism lives inservice_container.js. See customization-architecture.
Forked deps.
sequelizeis@techteamer/sequelize, andpassport-activedirectoryis pinned to a GitHub tarball —package.jsonis not all upstream npm..yarnrcsets--install.ignore-optional true.
TZ is forced to
Etc/UTCat process start (process-settings.js); don't rely on host timezone.
Lint gate is zero-warning (
--max-warnings 0) and executables must stay inbin/(audit CI fails otherwise). CI does not run the test suites on PRs.
Unverified / gaps
Surveyed structurally (listing + targeted sampling) rather than line-by-line — no per-file reading of these large subtrees:
server/service/(138 files): readservice_container.jsand confirmed names of ~40 key services + which are instantiated by each entrypoint’ssetupServices; did not read each service’s implementation.server/web/(179 files): confirmedWebServer.js/WebServerAuth.jsdesign, enumerated the 70+*.endpoint.jsand theapi/{role}split; did not read each endpoint/middleware.server/queue/(92 files): enumerated the six subfolders and harvested queue/RPC names from the entrypoints; did not read each handler body.server/cv/,server/ocr/,server/cron/,server/socket/,server/db/model/(61 models),server/backgroundProcess/: listed + sampled headers (CVApi.js); did not read every file.client/(1059),customization/(887),web/(build output),test/(21,939 incl. fixtures),db/migrate/(127 migrations),docs/config/schemas/(79 schemas): listed and counted; read representative manifests/schemas (main.json,dev.json, a few schema names) but not every file.- Not independently re-derived: the prose/output in
SYSTEM_CHECK.md(working-tree only) — its example output was not reproduced by runningcomptest.js; the scriptbin/comptest.jswas read directly. - Exact set/semantics of every config key: enumerable via
bin/config.getter.tsand the schema files, not exhaustively listed here.
Sources
Files/dirs actually read (via git show devel:<path> / git ls-tree devel, plus a few working-tree reads where noted):
- Root:
README.md,package.json,config.js,server.js,media.js,convert.js,cron.js,background.js,storage.js,integrationLog.js,babel.config.js,tsconfig.json,.yarnrc,.sequelizerc,.sequelize-config.js,jest.config.js,playwright.config.ts,docs.json,nginx_vuer_oss_docker.conf,supervisor_vuer_oss_docker.conf,changelog.md(head); working tree:SYSTEM_CHECK.md,jest.config-unit.js. bin/(read):build/build.js,script/script.task.js,script/script.compiler.js,style/style.task.js,style/style.compiler.js,watch/watch.js,server/server.task.js,validate-config.js,credits,convert,check,clean-rooms,create_rab_token.js,create-turn-static-auth-secret.js,archive,crypt-room,customer-key,migrate-rooms,storage.js,create-decrypted-room-copy.js,attachment.js,batch_restore.js,data-export.js,export_batch.js,export_customers.js,import-legacy-data.js,integration-log.js,migrate-folder-hierarchy.js,migrate-self-service-task-data,remove-empty-records-directories.js,self_service_settings_db_checker,timestamp-check.js,flow-editor.js,flow-extractor-portable.mjs,sms-logs-cli.mjs,comptest.js,config.getter.ts,doc.gen.ts,db/{create_user,create_user_batch,enable_user,create_standard_days,sync,truncate,migrate-data,migrate-rdbms.js,pg_sanity_check,customer-index.js},crypto/{decrypt,migrate}; listedcrypto/helpers/. Verifiedbin/instacash-cli.jsabsent on devel.server/:service_container.js,bootstrap/process-settings.js,bootstrap/connection/{db,redis,rabbitmq}.js,web/WebServer.js(head),cv/CVApi.js(head);git ls-treeofserver/,server/web,server/web/routes,server/web/api,server/web/api-doc,server/queue,server/cv,server/service,server/db/model.config/:dev.json(head),roles.json(head),docker.json(grep).docs/config/:main.json(head) +git ls-treeofschemas/(79 files).engines/:git ls-tree+build/build.js(head).db/:git ls-tree(migrate/= 127)..github/workflows/{pull-request.yaml,audit.yaml}.test/:git ls-treeoftest,test/tests,test/tests/unit; test-file counts.git -C … branch -a,git log --oneline,git remote -v.