ICE Platform — MEAN Stack Developer Technical Reference

Quick Orientation: What You Actually Work With

The codebase label says "MEAN stack" but the stack is actually:

Layer Technology Project
A Angular Angular 18.2 ice-visitor
E Express Express 4.15 ice-nodejs
N Node.js Node 18.20 (Volta-pinned) ice-nodejs
M MongoDB MS SQL Server (via mssql 3.3) ice-nodejs

There is no MongoDB. The "M" is MSSQL. Redis is also heavily used as a session store and message broker. MySQL (mysql 2.13) exists in package.json but MSSQL is the primary database — almost all dalayer.js operations go to SQL Server via stored procedures.


System Architecture Overview

Browser Visitor (Embedded Widget)
         │
         │  HTTP load page
         ▼
  ice-visitor (Angular 18, SPA)
         │
         ├── REST calls ──► ICEInternalAPI (ice-internal-api, .NET WebAPI)
         │                   └── reads/writes SQL Server
         │
         └── Socket.IO ──► ice-nodejs (Node + Express + Socket.IO)
                            ├── Redis (sessions, pub/sub, WFM queue)
                            ├── SQL Server (chat data, user state)
                            ├── Google Cloud Translate (real-time translation)
                            ├── Twilio (SMS-to-Chat)
                            ├── Twitter/Twit (Twitter DM channel)
                            └── FCM (mobile push notifications)

Agent / Supervisor (Operator Console)
         │  — separate Angular app: ice-mvc-admin
         │  — also connects to same ice-nodejs Socket.IO server
         │  — also calls same ice-internal-api endpoints

Admin (Configuration UI)
         └── ice-mvc-admin (ASP.NET MVC + JS)
              └── calls ice-internal-api

Two API namespaces the Angular code hits:

The Chat REST API documented at enterice.com/ICEAPI/api/ is a public/external API — your internal Angular code calls ICEInternalAPI instead.


ice-nodejs — Deep Dive

Entry Point and Boot Sequence

node app.js
    └── cluster.js                   ← Master process
         ├── forks N workers (cpu count)
         └── each worker runs:
              expressserver.js       ← Express + HTTPS server
                   └── socket.js    ← Socket.IO initialization

cluster.js (ice-nodejs/cluster.js):

expressserver.js (ice-nodejs/expressserver.js):

Configuration System

config/ directory uses nconf for layered config:

File Purpose
config/development.json Local dev: port 3000, local Redis/SQL
config/production.json Prod settings
config/flashfree.json Flash-free deployment variant
config/language.json Language code mappings

Key config keys (from development.json):

{
  "node": { "env": "development" },
  "express": { "port": 3000 },
  "sqlConn": {
    "user": "...", "password": "...",
    "server": "...", "database": "EChat_Staging",
    "connectionTimeout": 1500000, "requestTimeout": 1500000,
    "pool": { "max": 20, "min": 1, "idleTimeoutMillis": 30000 }
  },
  "ApplicationRedis": { "server": "127.0.0.1", "port": "6379" },
  "api": {
    "url": "https://dev.enterice.com/ICEInternalApi/api/Visitor/SaveVisitorInformationFromApi",
    "secretKey": "ASDFGHJK@#$%^l"
  }
}

socket.js — The God Node

socket.js is 1.5 MB. It is the runtime core of the entire chat platform. Changing anything here has blast radius across every connected client. It owns:

Socket.IO setup:

// socket.js attaches to the HTTPS/HTTP server Express created
// Uses Redis pub/sub adapter so all cluster workers share socket state
io.adapter(RedisStore({ pubClient: pubR, subClient: subR }));

Language support hardcoded at top of socket.js:

const language = ['English', 'Polish', 'Spanish', 'French', 'Czech', 'Arabic', 'Japanese'];

Seven languages for system messages (join messages, greetings, transcript labels) are stored as parallel arrays. Real-time translation uses Google Cloud Translate API (@google-cloud/translate v2).

Key functions in socket.js:

Function Purpose
initialize(server, cluster) Bootstraps Socket.IO, attaches event handlers
eWfmRedisPush() Pushes Workforce Management (WFM) real-time data to Redis
RedisOperationCustomer() Redis operations for visitor/customer state
RedisOperationOperator() Redis operations for agent state
getQueue() Retrieves chat queue data
getWFMData() Fetches WFM RTA data (most connected function, 9 edges)
PerformCallQueueCleanup() Cleans up stale queue entries
translateText() Calls Google Translate for real-time message translation
detectLanguage() Detects language of incoming message

Third-party integrations wired inside socket.js:

Data Access Layer

dalayer.js (97 KB — the main DB module):

Example pattern:

async function updateUserStatus(userId, agentState, currentIp, inCall, callback) {
    var connection = await new sql.Connection(sqlconn);
    connection.connect(async function(err) {
        var request = await new sql.Request(connection);
        request.input('userID', sql.Int, userId);
        request.input('agentState', sql.VarChar, agentState);
        // ...
        request.execute('usp_usersUpdateNew', async function(err, recordsets, returnValue) {
            connection.close();
            await callback(err);
        });
    });
}

dalayer_repo.js (31 KB): Additional DB operations, same pattern as dalayer.js.

serverChatDBLogic.js: DB operations specific to the chat button/visitor identification flow. Called by the /ChatBtn POST route.

Session & Auth (Redis-backed)

login_process.js — Handles agent logout via a Redis-based message queue:

Redis DB 15, key: "login-list"
      │
      │ BRPOP (blocking pop — waits for messages)
      ▼
login_process.js.waitForPush()
      │
      ├── action == "logoutUser"    → SMEMBERS {user}{orgid} → logoutSession() for each
      └── action == "logoutSession" → GET session:{id} → logoutSession() after 30s delay

logoutSession(user, orgid, session):

  1. SREM {user}{orgid} {session} — remove session from user's set
  2. DEL session:{session} — delete session data
  3. PUBLISH 'logout' { uuid: session } — broadcast to all workers via Redis pub/sub

This means a force-logout from the Admin UI results in a Redis message → login_process.js picks it up → publishes to logout channel → all Socket.IO workers disconnect that session.

Survey / Bot Process

sandra_process.js (19 KB):

IceSandraTestChat.js — Core chat session management:

Rule Engine

RuleConditionHandler.js (13 KB) — Called by the /RuleServer POST route:

RuleEngineDb.js (16 KB) — DB operations for rule engine (load rules, log invitations).

IceRuleEngine.js (1.7 KB) — Orchestrator that ties RuleConditionHandler and RuleEngineDb together.

chatbuttonscript/RuleHandler.js — Client-side counterpart: runs in the visitor's browser, periodically POSTs context to the /RuleServer endpoint.

Logging

logger.js:

WFM Integration (Assurant-specific)

Assurant_WFM_RTA/ — A sub-module for workforce management Real-Time Adherence integration with Assurant's WFM system. Connected to socket.js via getWFMData() / eWfmRedisPush(). Pushes live agent stats to the WFM system in real time.

Start the Server Locally

cd ice-nodejs
# Set NODE_ENV=development so it reads config/development.json
NODE_ENV=development node cluster.js

Dependencies for local dev:


ice-visitor — Deep Dive

What It Is

ice-visitor is the embedded chat widget — the Angular 18 SPA that gets injected into a customer's website. When a visitor opens a page with the ICE chat button, this Angular app loads inside an iframe or as an embedded component.

Project name in package.json: vs-console (Visitor Console).

Application Bootstrap

main.ts
  └── platformBrowserDynamic().bootstrapModule(AppModule)
        └── AppModule (app.module.ts)
              └── RouterModule (HashLocationStrategy)
                    ├── /visitor  → VisitorComponent
                    ├── /chatbot  → ChatbotComponent
                    └── /blank    → BlankComponent

Hash-based routing (#/visitor) because this app runs embedded in third-party pages — hash routing avoids server-side routing conflicts.

Environment Configuration: AppSettings

src/app/config/app.settings.ts — no Angular environment.ts files are used. Instead, AppSettings.getEnvironmentVariable(key) switches on window.location.hostname:

Hostname Environment
localhost Local dev (Node on :3000, internal API on localhost)
dev.enterice.com Development server
staging.enterice.com Staging server
ha.enterice.com High-availability / production alternate
enterice.com / www.enterice.com Production
chat.enterice.com Chat-specific production domain

URL keys returned by getEnvironmentVariable():

Key Points To
API_URL https://{env}/ICEInternalAPI/Api/ — ice-internal-api (.NET REST)
NODE_API_URL https://{env}/api/ — ice-nodejs REST routes
API_EMAILURL https://{env}/ICEInternalApi/Email/api/ — email API
SOCKET_URL https://{env} — Socket.IO connection base URL
LOGINURL Agent login page
LOGOUTURL Agent logout URL
KB_BOT Array of Knowledge Base bot Skill IDs
DEV_KB_Server AI knowledge base API endpoint (qevalpro.ai)

For local dev: NODE_API_URL = 'http://127.0.0.1:3000/api/' and SOCKET_URL = 'http://127.0.0.1:3000' — so ice-nodejs must be running locally.

Service Architecture

VisitorService (src/app/visitor.service.ts)

The central service — every component talks to the server through it. Two responsibilities:

1. HTTP calls to API_URL (ice-internal-api):

// Pattern: encrypt body → POST → decrypt response
getToken(orgId): Observable<any>   // GET visitor JWT token
offlineSurveyExist(SkillID)        // check if offline survey configured
exitSurveyExist(SkillID)           // check exit survey
prechatSurveyExist(SkillID)        // check pre-chat survey
getSubcampaignDetails(SkillID)     // load campaign/skill config
// ... many more

2. Socket.IO connection to SOCKET_URL (ice-nodejs):

// VisitorService.socket = io.connect(AppSettings.getEnvironmentVariable('SOCKET_URL'))
// All real-time events go through this single socket instance

Encryption: every request body is encrypted via utilService.encryptRequest() and every response is decrypted via utilService.decryptResponse() — using crypto-js (AES). If you inspect network traffic, request/response bodies will be ciphertext.

UtilService (src/app/util.service.ts)

Utility helpers used across the app:

ModalService (src/app/modal.service.ts)

Controls three modals:

Component Hierarchy

AppComponent (shell, handles routing)
  └── Router Outlet
        ├── VisitorComponent (186 edges — god node of Angular side)
        │     ├── Uses: VisitorService, UtilService, ModalService
        │     ├── Controls all socket event handlers
        │     ├── Manages chat message flow
        │     └── Triggers: ChatbotComponent, all Modals
        │
        ├── ChatbotComponent (18 edges)
        │     └── Bot intent/story flow before human handoff
        │
        └── BlankComponent (placeholder/loading state)

Additional (standalone routes from AppModule):
  ├── ChatbotsurveyComponent → post-chatbot survey flow
  └── PaypalComponent        → payment-via-chat flow

VisitorComponent has 186 graph edges — it directly references almost everything. This is normal for a single-page chat widget where one component manages the entire chat session lifecycle.

Module Imports (from app.module.ts)

Module Purpose
RouterModule + HashLocationStrategy Routing with hash URLs
ModalModule (ngx-bootstrap) Bootstrap modal dialogs
NgSelectModule Dropdown selects (skill selection, etc.)
NgxSliderModule Sliders (volume control, etc.)
NgScrollbarModule Custom scrollbar for chat transcript
SocialLoginModule Google/Facebook login (currently commented out)
BlockUIModule Loading overlay while API calls resolve
TranslateModule i18n via ngx-translate, loads from assets/i18n/*.json
FormsModule + ReactiveFormsModule Template + reactive forms

Start the App Locally

cd ice-visitor
npm install
npm start   # ng serve → http://localhost:4200

With localhost hostname, AppSettings auto-selects local dev URLs. Requires ice-nodejs running on port 3000 and ice-internal-api (.NET) accessible on localhost.

Build for Production

ng build   # output to dist/vs-console/

The built files are served by the ASP.NET host — the Angular app lives inside the .NET host's wwwroot or equivalent directory.


How ice-visitor Talks to ice-nodejs

Socket.IO Event Flow (Chat Session Lifecycle)

Visitor opens page
  │
  ├─ VisitorService calls API_URL/Visitor/GenerateTokenForVisitor
  │   → gets visitor JWT token from ice-internal-api
  │
  ├─ VisitorService connects Socket.IO to SOCKET_URL
  │   → ice-nodejs socket.js accepts connection
  │
  ├─ VisitorComponent emits socket event (e.g. "initChat")
  │   → ice-nodejs processes, queries SQL Server via dalayer.js
  │   → ice-nodejs emits back confirmation
  │
  ├─ Agent accepts chat (from operator console)
  │   → ice-nodejs emits to visitor socket: "operatorJoined" (or similar)
  │   → VisitorComponent updates UI to show agent connected
  │
  ├─ Chat messages: emit/on "message" events bidirectionally
  │
  └─ Chat ends: dispose events → ice-nodejs saves transcript via dalayer.js

REST Calls from ice-visitorice-internal-api (NOT ice-nodejs)

Most configuration and survey data comes from the .NET API (ICEInternalAPI/Api/), not ice-nodejs. Examples from VisitorService:

// Visitor/GenerateTokenForVisitor — get auth token
// Visitor/CheckPrechatSurveyExist — does a pre-chat survey exist for this skill?
// Visitor/CheckExitSurveyExist
// Visitor/CheckOfflineSurveyExist

All these POST to ICEInternalAPI/Api/Visitor/{Action} with encrypted bodies.


The Four Projects at a Glance

Project Stack Your Involvement
ice-nodejs Node.js 18, Express 4, Socket.IO 2, Redis, MSSQL High — real-time chat server, all socket logic
ice-visitor Angular 18, TypeScript 5.4 High — visitor chat widget
ice-internal-api .NET 6 WebAPI, C# Low — you'll call it but probably not modify it
ice-mvc-admin ASP.NET MVC + vanilla JS Low — admin UI, not your domain

Verification of the Two Shared .md Files

01-ICE-Product-Overview.md — Verdict: Mostly Accurate

Claim Status Notes
Browser-based, no client install Correct
SSL/256-bit encrypted chat Correct PFX cert in expressserver.js, HSTS via helmet
Role IDs: 1=Admin, 2=Agent, 3=Supervisor Correct Confirmed in CreateUser API + app.settings.ts (OPERATOR_PERMISSION = 2)
JWT-style Token returned by Login Correct jsonwebtoken in ice-nodejs deps
Rule engine on client side Correct chatbuttonscript/RuleHandler.js runs in browser, POSTs to /RuleServer
Bot is intent/story-based, not LLM Partially outdated sandra_process.js handles classic intent bot; app.settings.ts now has DEV_KB_Server pointing to qevalpro.ai (an LLM endpoint) — AI KB integration exists
Proactive chat via Rule Engine Correct
Socket.io for real-time Not mentioned — worth knowing

02-ICE-API-Reference.md — Verdict: Accurate for Public API; Incomplete for Internal

Claim Status Notes
POST https://www.enterice.com/ICEAPI/api/... base URL Correct for public API Angular code hits ICEInternalAPI/Api/ (different namespace)
Token in body, not header Correct Confirmed from source
OrganisationID on Login, OrganizationID elsewhere Correct — genuine inconsistency Handle with case-insensitive parsing
Message: "Success" vs message: "Failed" inconsistency Correct
GetChatTranscript is GET, others are POST Correct
Three separate auth tokens (Chat/Email/Data API) Correct
No MongoDB — MSSQL is the DB Not stated DB is MSSQL with stored procedures, NOT MongoDB
Socket.IO API not documented Gap The md files cover REST only; real-time events via Socket.IO are undocumented
ice-nodejs serves NODE_API_URL (/api/) Not mentioned The visitor widget calls two separate API namespaces

Common Tasks as a New Developer

Find where a Socket event is handled

# All socket events are in socket.js
grep -n "socket.on\|io.on" ice-nodejs/socket.js | head -40

Trace an API call from Angular to backend

  1. Find the method in VisitorService
  2. Check whether URL uses API_URL (→ ice-internal-api, .NET) or NODE_API_URL (→ ice-nodejs)
  3. If NODE_API_URL: find Express route in expressserver.js — it's either /ChatBtn or /RuleServer
  4. If API_URL: you're in ice-internal-api territory (C#, separate project)

Add a new API call in ice-visitor

// In visitor.service.ts, follow existing pattern:
myNewCall(param: string): Observable<any> {
    let body = { 'Token': this.utilService.getLocalData('visitorToken'), 'MyParam': param };
    const encryptedBody = this.utilService.encryptRequest(body);
    return this.http.post(
        AppSettings.getEnvironmentVariable('API_URL') + 'Visitor/MyNewEndpoint',
        encryptedBody,
        { headers: this.headers }
    ).pipe(map((response: any) => {
        if (response) return this.utilService.decryptResponse(response);
        return response;
    }));
}

Read ice-nodejs logs

ice-nodejs/ErrorLogs/Errorlogs.log   ← rotates daily

Socket.io version constraint

ice-visitor uses socket.io-client 2.4.0. ice-nodejs uses socket.io 2.0.2. Do not upgrade either independently — the client and server socket.io major versions must stay matched. A 2.x client is incompatible with a 3.x/4.x server.