# Crash Report Endpoint Clients (POS apps running in a browser or Node.js) can submit crash / uncaught-error details to the sync server for later inspection. The server writes each report to a plain-text file under `logs/crash/`. ``` POST /v1/crashreport ``` Paths may also be called as `/api/v1/crashreport` — both resolve to the same handler. --- ## Request - **Method:** `POST` - **Content-Type:** `application/json` - **Body:** any JSON object you want to record. Keep it free-form; common fields are shown below. ```json { "app": "my-pos-app", "version": "1.2.3", "platform": "win32", "userAgent": "Mozilla/5.0 ...", "message": "Cannot read properties of undefined (reading 'price')", "stack": "TypeError: Cannot read properties of undefined (reading 'price')\\n at ...", "context": { "currentView": "checkout", "orderId": "abc-123" }, "device": { "id": "clerk-01", "display": 1920 } } ``` Nothing is validated server-side: the body is logged as-is (`JSON.stringify(body, null, 2)`). --- ## Responses | Status | Body | Meaning | |---|---|---| | `200` | `{ "Message": "OK", "file": "-.txt" }` | Report saved | | `400` | `{ "Message": "Invalid JSON body." }` | Body is not valid JSON | Non-JSON or empty bodies are treated as follows: - **Empty body** → saved as an empty object `{}`. - **Invalid JSON** (e.g. a raw text dump) → rejected with `400`. --- ## Log file format Each accepted report is written to: ``` logs/crash/-.txt ``` - `` — local server time, `YYYYMMDD-HHMMSS`. - `` — the client's remote address (`::ffff:` IPv4 prefix is stripped; `:` is replaced with `_`). Two reports from the same client within the same second overwrite each other. Include a unique id inside the body if you need to guarantee every report is preserved. Example file: ``` timestamp: 2026-08-17T09:41:22.731Z ip: ::ffff:127.0.0.1 user-agent: node-fetch/1.0 (+https://github.com/bitinn/node-fetch) { "app": "my-pos-app", "message": "Cannot read properties of undefined", "stack": "TypeError: ..." } ``` The log directory `logs/crash/` is created automatically on first use. --- ## Usage from Node.js ```js // plain fetch (Node 18+) const baseUrl = 'https://192.168.1.10:4443'; const AUTH_TOKEN = ''; // optional if your server does not enforce it fetch(`${baseUrl}/v1/crashreport`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ app: 'my-pos-app', version: '1.2.3', message: error.message, stack: error.stack, }), }) .then(async (res) => console.log(res.status, await res.json())) .catch((err) => console.error('could not send crash report', err)); ``` Because the server uses a self-signed certificate, set `rejectUnauthorized: false` when using `node-fetch` or Node's `https` module: ```js import https from 'node:https'; import fetch from 'node-fetch'; const agent = new https.Agent({ rejectUnauthorized: false }); fetch('https://:4443/v1/crashreport', { method: 'POST', agent, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ app: 'my-pos-app', message: error.message, stack: error.stack }), }) .then(async (res) => console.log(res.status, await res.json())); ``` Hook it into your global error handlers so nothing is lost: ```js process.on('uncaughtException', (err) => { void sendCrashReport(err); // ...your own logging / shutdown }); process.on('unhandledRejection', (reason) => { void sendCrashReport(reason instanceof Error ? reason : new Error(String(reason))); }); ``` --- ## Usage from the browser Use `fetch` with `mode: 'no-cors'` **or** a normal `cors` request — the server sends `Access-Control-Allow-Origin: *`, so a plain `fetch` works from any origin. ```js function sendCrashReport(payload) { return fetch('https://:4443/v1/crashreport', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) .then(async (res) => { const json = await res.json(); console.log(`crash report ${res.ok ? 'saved' : 'failed'}`, json); return json; }) .catch((err) => console.error('could not send crash report', err)); } ``` Wire it into `window.onerror` and `unhandledrejection`: ```js window.addEventListener('error', (event) => { sendCrashReport({ app: 'my-pos-app', version: appVersion, message: event.message, stack: event.error?.stack, context: { url: location.href }, }); }); window.addEventListener('unhandledrejection', (event) => { sendCrashReport({ app: 'my-pos-app', version: appVersion, message: event.reason?.message || String(event.reason), stack: event.reason?.stack, }); }); ``` > **Note:** if you use `mode: 'no-cors'` the browser will send the request but you will not be able to read the JSON response body — the report is still saved server-side.