Files
jtlPosSync/crashreport.md
sebseb7 390d66890b u
2026-08-18 00:55:12 +00:00

4.9 KiB

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.
{
  "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": "<timestamp>-<ip>.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/<timestamp>-<ip>.txt
  • <timestamp> — local server time, YYYYMMDD-HHMMSS.
  • <ip> — 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

// plain fetch (Node 18+)
const baseUrl = 'https://192.168.1.10:4443';
const AUTH_TOKEN = '<your pairing 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:

import https from 'node:https';
import fetch from 'node-fetch';

const agent = new https.Agent({ rejectUnauthorized: false });

fetch('https://<host>: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:

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.

function sendCrashReport(payload) {
  return fetch('https://<host>: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:

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.