# node8443 User Guide

Complete guide for the **Peopleware WebSocket Server** — the bridge between C3 SDK biometric devices (AiFace / A107F) and **PeopleSync v2**.

---

## Table of contents

1. [What this service does](#1-what-this-service-does)
2. [Architecture](#2-architecture)
3. [Prerequisites](#3-prerequisites)
4. [Installation](#4-installation)
5. [Configuration](#5-configuration)
6. [Running the server](#6-running-the-server)
7. [Registering devices in PeopleSync](#7-registering-devices-in-peoplesync)
8. [How devices connect](#8-how-devices-connect)
9. [Attendance (check-in / check-out)](#9-attendance-check-in--check-out)
10. [User enrollment sync](#10-user-enrollment-sync)
11. [Testing from your Windows PC](#11-testing-from-your-windows-pc)
12. [Managing devices from the server console](#12-managing-devices-from-the-server-console)
13. [PeopleSync API integration](#13-peoplesync-api-integration)
14. [Logging](#14-logging)
15. [Troubleshooting](#15-troubleshooting)
16. [Project layout](#16-project-layout)

---

## 1. What this service does

`node8443` is a Node.js WebSocket server that:

- Accepts connections from C3 SDK attendance devices over **WebSocket** (default port **8443**).
- Verifies each device against PeopleSync using **serial number + MAC address**.
- Forwards **attendance events** (check-in / check-out) to PeopleSync.
- Syncs **user enrollment data** (fingerprint, password, card, face) from devices to PeopleSync.
- Logs **connection lifecycle** (connected / disconnected) to PeopleSync.
- Can send **commands back to devices** (set time, add users, query status) when run interactively or via helper scripts.

Devices must already exist in PeopleSync before they can register. Unknown devices are rejected.

---

## 2. Architecture

```
┌─────────────────┐     WebSocket      ┌──────────────────┐     HTTPS API      ┌─────────────────┐
│  C3 SDK Device  │ ─────────────────► │   node8443.js    │ ─────────────────► │  PeopleSync v2  │
│  (A107F / AiFace)│   ws://host:8443   │  (this project)  │  X-API-Key header │  Laravel API    │
└─────────────────┘                    └──────────────────┘                    └─────────────────┘
```

**Typical deployment**

| Component | Example |
|-----------|---------|
| PeopleSync API | `https://peoplesync.clairemontferrond.net` |
| WebSocket server (EC2) | `ws://3.0.38.207:8443` |
| Device | Configured to connect to EC2 host + port 8443 |

---

## 3. Prerequisites

- **Node.js** 18+ (24 tested)
- **npm**
- A running **PeopleSync v2** instance with the device registered
- **`SERVICE_API_KEY`** — must match the value in PeopleSync's `.env` exactly (32 characters; may contain special characters like `"`, `£`, `~`)
- EC2 security group (or firewall) allowing inbound **TCP 8443** if devices connect over the internet

---

## 4. Installation

```bash
git clone https://github.com/tongsonj/node-peopleware.git
cd node-peopleware
git checkout CTE          # or your deployment branch
npm install
cp .env.example .env      # then edit .env
```

---

## 5. Configuration

Create `.env` in the project root (same folder as `node8443.js`).

```env
# PeopleSync API
LARAVEL_API_URL=https://peoplesync.clairemontferrond.net
SERVICE_API_KEY='AyN^b"5@£Lm1]"uK~xI4gj*MgR)+e5*a'

# WebSocket server
WEBSOCKET_PORT=8443
WEBSOCKET_HOST=0.0.0.0

# Optional global tenant override (usually not needed — tenant comes from device record)
# TENANT_ID=1

# Logging
LOG_LEVEL=info
LOG_MAX_SIZE=20m
LOG_MAX_FILES=14d
LOG_TO_CONSOLE=true
LOG_TO_FILE=true
```

### Important notes

| Setting | Notes |
|---------|-------|
| `SERVICE_API_KEY` | Wrap in **single quotes** if the key contains `"` or other shell-special characters. The server strips surrounding quotes on load. |
| `WEBSOCKET_HOST` | Use `0.0.0.0` to listen on all interfaces. Do **not** put a URL here (e.g. `http://127.0.0.1` will crash the server). |
| `LARAVEL_API_URL` | No trailing slash. Must point to PeopleSync, not an old hostname. |
| `.env` location | Loaded from the **script directory** (`__dirname`), not PM2's cwd — safe for PM2 deploys. |

On startup you should see:

```
API key loaded: 32 chars
Env file: /path/to/node-peopleware/.env
WebSocket server is ready and listening on port 8443
```

If key length is wrong or `MISSING`, fix `.env` and restart.

---

## 6. Running the server

### Local development

```bash
npm start
# or with auto-reload:
npm run dev
```

### Production on EC2 (PM2)

```bash
cd ~/public_html/node8443.clairemontferrond.net
git pull origin CTE
pm2 delete node8443
pm2 start node8443.js --name node8443 --cwd ~/public_html/node8443.clairemontferrond.net
pm2 save
pm2 logs node8443
```

After changing `.env` on EC2, always restart PM2 (`pm2 delete` + fresh start) so the new values load.

### Verify API connectivity (on the server)

```bash
node -e "
require('dotenv').config({ path: '.env', override: true });
let k = process.env.SERVICE_API_KEY || '';
if (k.startsWith(\"'\") || k.startsWith('\"')) k = k.slice(1, -1);
const https = require('https');
const data = JSON.stringify({ serial_number: 'YOUR_SN', mac_address: 'YOUR-MAC' });
const req = https.request({
  hostname: 'peoplesync.clairemontferrond.net',
  path: '/api/v1/devices/check',
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data), 'X-API-Key': k }
}, res => { let b=''; res.on('data',d=>b+=d); res.on('end',()=>console.log(res.statusCode, b)); });
req.write(data); req.end();
"
```

Expected: `200 {"device_id":...,"tenant_id":...}`

---

## 7. Registering devices in PeopleSync

Before a physical device (or simulator) can connect, register it in **PeopleSync admin**:

1. Add the device with the correct **serial number** and **MAC address**.
2. Assign it to a **tenant**.
3. Ensure serial + MAC in the device firmware/network settings match exactly.

The server calls `POST /api/v1/devices/check` with both fields. **Both must match** — a correct serial with a wrong MAC is rejected.

### Test device (for simulation)

| Field | Value |
|-------|-------|
| Serial | `TESTWS0018443` |
| MAC | `00-99-84-43-00-01` |
| Enroll ID | `10` |

---

## 8. How devices connect

### Registration flow

1. Device opens WebSocket to `ws://<host>:8443`.
2. Device sends `reg` command with serial number and `devinfo.mac`.
3. Server checks PeopleSync → if found, responds with `result: true`, `tenant_id`, `device_id`.
4. Server logs connection to PeopleSync, syncs device clock to **Manila time (UTC+8)**.
5. Device can now send `sendlog`, `senduser`, etc.

### `reg` request (device → server)

```json
{
  "cmd": "reg",
  "sn": "TESTWS0018443",
  "devinfo": {
    "mac": "00-99-84-43-00-01",
    "modelname": "AiFace",
    "firmware": "1.0.0",
    "useduser": 1,
    "time": "2026-06-15 18:00:00"
  }
}
```

### `reg` response (server → device)

**Success:**

```json
{
  "ret": "reg",
  "result": true,
  "cloudtime": "2026-06-15 18:00:01",
  "tenant_id": 1,
  "device_id": 2
}
```

**Failure codes (`reason` field):**

| reason | Meaning | Action |
|--------|---------|--------|
| `1` | Missing serial number | Fix device config |
| `2` | API auth failed or incomplete registration | Check `SERVICE_API_KEY` in `.env` |
| `3` | Device not found in PeopleSync | Register device (serial + MAC) |

After `reason: 3`, the connection stays open but **all messages are ignored** until the device sends a new `reg`.

---

## 9. Attendance (check-in / check-out)

### `sendlog` request (device → server)

```json
{
  "cmd": "sendlog",
  "sn": "TESTWS0018443",
  "count": 1,
  "logindex": 0,
  "record": [
    {
      "enrollid": 10,
      "name": "EMP-0010",
      "time": "2026-06-15 18:00:00",
      "mode": 15,
      "inout": 0,
      "event": 0
    }
  ]
}
```

| Field | Meaning |
|-------|---------|
| `inout` | `0` = check-in, `1` = check-out |
| `mode` | Verification method on device (`15` = face, `10` = password, `11` = card, `1–9` = fingerprint) |
| `enrollid` | User ID on the device |
| `time` | Manila time `YYYY-MM-DD HH:MM:SS` |

### What the server sends to PeopleSync

Each record becomes `POST /api/v1/events` with:

- `event_type`: `check_in` or `check_out`
- `tenant_id`, `device_id`, `device_identifier` (serial)
- `user_identifier` (enrollid), `user_name`
- `verification_method`, `device_model` (`A107F`), `result` (`success`)
- `event_time` and `metadata` (direction, verify mode, temperature, etc.)

### `sendlog` response

```json
{
  "ret": "sendlog",
  "result": true,
  "count": 1,
  "cloudtime": "2026-06-15 10:00:01"
}
```

---

## 10. User enrollment sync

When users are enrolled on the device (fingerprint, password, card, or face), the device sends `senduser`. The server parses the data and calls:

`POST /api/v1/device-users/enrollments`

### Authentication types (`backupnum`)

| backupnum | Type |
|-----------|------|
| 0–9 | Fingerprint slot |
| 10 | Password |
| 11 | Card / RFID |
| 20–27, 50 | Face |

Enrollment is upserted in PeopleSync per tenant + enroll ID + auth type.

---

## 11. Testing from your Windows PC

You can simulate a real device connection from your PC to EC2 (port 8443 must be open).

### Option A — Interactive PowerShell script (recommended)

```powershell
cd C:\projects\node-peopleware
.\simulate-attendance.ps1
```

Prompts for **employee name** and **check-in / check-out**, then connects to EC2.

Default targets: `ws://3.0.38.207:8443`, test device `TESTWS0018443` / `00-99-84-43-00-01`.

Override defaults:

```powershell
.\simulate-attendance.ps1 -WsHost 3.0.38.207 -Serial YOUR_SN -Mac 00-01-A9-02-FC-C2 -EnrollId 10
```

### Option B — Node simulator directly

```bash
node test_simulate_checkin.js \
  --host 3.0.38.207 \
  --port 8443 \
  --sn TESTWS0018443 \
  --mac 00-99-84-43-00-01 \
  --enrollid 10 \
  --name "Employee Name" \
  --inout 0
```

| Flag | Meaning |
|------|---------|
| `--inout 0` | Check-in (default) |
| `--inout 1` | Check-out |

### Expected success output

```
<< {"ret":"reg","result":true,"tenant_id":1,"device_id":2,...}
<< {"ret":"sendlog","result":true,"count":1,...}
```

Verify the event appears in PeopleSync attendance records.

### Other test utilities

| Script | Purpose |
|--------|---------|
| `test_command.js` | Send a command to an already-connected device (e.g. `gettime`) |
| `test_photo_upload.js` | Test face photo upload via `DeviceTester` (device must be connected) |

```bash
node test_command.js DEVICE_SERIAL gettime
```

---

## 12. Managing devices from the server console

When you run `node node8443.js` in a **terminal** (TTY), an interactive command prompt appears (`>`). This is **not** available under PM2 without a TTY.

### Common commands

| Command | Description |
|---------|-------------|
| `list` | Show connected devices |
| `send <serial> gettime` | Get device time |
| `settime-manila <serial>` | Set device clock to current Manila time |
| `adduser <serial> <id> name="John" password="1234"` | Register user on device |
| `setusername <serial> <id> name="New Name"` | Rename user on device |
| `modifyprivilege <serial> <id> <0-3>` | Change privilege level |
| `deleteuser <serial> <id>` | Delete user from device |
| `getusername <serial> <id>` | Get user name from device |
| `getuser <serial> <id> <backupnum>` | Get auth data (10=password, 11=card, 0-9=fp) |
| `getuserfull <serial> <id>` | Fetch name + all auth methods |
| `send <serial> getstatus type=2` | Device status (type 2 = total users) |
| `help` | Full command list |
| `quit` | Exit command mode (server keeps running) |

### Privilege levels

| Value | Role |
|-------|------|
| 0 | General user |
| 1 | Super administrator |
| 2 | Registration administrator |
| 3 | Configuration administrator |

### User registration tips

- `adduser` with `backupnum=10` registers a **password** user.
- `adduser` with `card="12345678"` registers a **card** user.
- `backupnum=0` without fingerprint data creates a visible user who **cannot authenticate** until enrolled on-device.
- Face enrollment (`backupnum` 20–27) often **cannot be done remotely** — enroll on the device or upload templates per vendor docs.
- The server may temporarily disable attendance mode (`enabledevice flag=0`) during remote user registration, then re-enable it.

---

## 13. PeopleSync API integration

All calls use header **`X-API-Key: <SERVICE_API_KEY>`** and optional **`X-Tenant-ID`** when scoped.

| Method | Endpoint | Purpose |
|--------|----------|---------|
| POST | `/api/v1/devices/check` | Verify device (serial + MAC) → `{ device_id, tenant_id }` |
| POST | `/api/v1/events` | Save attendance event |
| POST | `/api/v1/device-connections` | Log connection (`status: connected`) |
| PUT | `/api/v1/device-connections/{id}` | Update on disconnect (`status: disconnected`) |
| POST | `/api/v1/device-users/enrollments` | Upsert enrollment from device |

### HTTP client

The server uses Node's native **`http`/`https`** modules (not Axios). This is required because API keys containing `"` and other special characters were rejected when sent via Axios/fetch.

---

## 14. Logging

Logs are written to the `logs/` directory with daily rotation:

| File | Contents |
|------|----------|
| `app-YYYY-MM-DD.log` | All log levels |
| `error-YYYY-MM-DD.log` | Errors only |

Configure via `.env`:

```env
LOG_LEVEL=info          # Use debug for troubleshooting
LOG_MAX_SIZE=20m
LOG_MAX_FILES=14d       # Auto-delete after 14 days
LOG_TO_CONSOLE=true
LOG_TO_FILE=true

# Verbose options (debug only)
# LOG_WEBSOCKET_MESSAGES=true
# LOG_API_REQUESTS=true
# LOG_API_RESPONSES=true
```

Under PM2: `pm2 logs node8443`

---

## 15. Troubleshooting

### Port already in use (`EADDRINUSE :8443`)

Another process is using the port.

**Windows:**

```powershell
netstat -ano | findstr :8443
taskkill /PID <pid> /F
```

**Linux:**

```bash
sudo lsof -i :8443
kill <pid>
```

Or change `WEBSOCKET_PORT` in `.env`.

### `reg result: false`, reason `2` — authentication failed

- `SERVICE_API_KEY` in node8443 `.env` does not match PeopleSync `.env`.
- Key corrupted by encoding (`Â£` instead of `£`, wrong `~` character).
- On PeopleSync after key change: `php artisan config:clear && php artisan config:cache`.
- Restart PM2 after fixing `.env`.

Check startup log: `API key loaded: 32 chars` (must match PeopleSync key length).

### `reg result: false`, reason `3` — device not registered

- Serial or MAC mismatch — register/fix device in PeopleSync admin.
- MAC format must match exactly (e.g. `00-99-84-43-00-01` vs `00:99:84:43:00:01`).

### `sendlog result: false`

- Device not registered on this connection (send `reg` first).
- PeopleSync API error — check `pm2 logs` for `POST /api/v1/events` status.

### WebSocket connection refused from Windows

- EC2 security group must allow **inbound TCP 8443** from your IP.
- Confirm server is running: `pm2 status`.

### `WEBSOCKET_HOST` crash (`ENOTFOUND`)

Never set `WEBSOCKET_HOST` to a URL. Use `0.0.0.0` or a bind address like `127.0.0.1`.

### Disk space / large logs

- Set `LOG_LEVEL=info` in production (not `debug`).
- `LOG_MAX_FILES=14d` rotates and deletes old logs automatically.
- `logs/` is gitignored.

### PM2 shows old API key after `.env` change

```bash
pm2 delete node8443
pm2 start node8443.js --name node8443 --cwd /full/path/to/project
```

Do not rely on `pm2 restart` alone after env changes.

---

## 16. Project layout

```
node-peopleware/
├── node8443.js              # Main WebSocket server entry point
├── simulate-attendance.ps1  # Windows attendance simulator (interactive)
├── test_simulate_checkin.js # Node attendance simulator
├── test_command.js          # Send commands to connected devices
├── test_photo_upload.js     # Face photo upload test
├── .env.example             # Environment template
├── package.json
├── src/
│   ├── ConnectionManager.js # Device connection + outbound commands
│   ├── MessageHandler.js    # Incoming message routing (reg, sendlog, senduser)
│   ├── LaravelClient.js     # PeopleSync HTTP API client
│   ├── Logger.js            # Winston logging with rotation
│   └── DeviceTester.js      # Photo upload test helpers
├── docs/
│   └── USER_GUIDE.md        # This document
└── logs/                    # Rotated log files (auto-created)
```

---

## Quick reference card

```bash
# Install & configure
npm install && cp .env.example .env

# Run locally
npm start

# Deploy EC2
git pull origin CTE && pm2 delete node8443 && pm2 start node8443.js --name node8443 --cwd $PWD && pm2 save

# Simulate attendance (Windows)
.\simulate-attendance.ps1

# Watch logs
pm2 logs node8443
```

For PeopleSync-side API details, see `peoplesync-v2/docs/external-integrations.md` in the PeopleSync repository.
