Files
kooltab-backoffice/scripts/device-sync.ts
T
2026-09-19 22:19:35 +02:00

206 lines
5.8 KiB
TypeScript

/**
* KoolTab Device Simulator
*
* A CLI tool that simulates a Flutter bar terminal device.
* Usage: npx ts-node scripts/device-sync.ts
*
* Steps:
* 1. Register a device (or paste an existing token)
* 2. Sync open tabs
* 3. Receive pending commands
* 4. Acknowledge commands
*/
const API_URL = process.env.API_URL || 'http://localhost:3001/api/v1';
const args = process.argv.slice(2);
const command = args[0] || 'help';
interface SyncResult {
cursor: string;
commands: Array<{
id: string;
type: string;
tabId: string;
expectedVersion: number;
paymentMethod: string;
requestedBy: string;
createdAt: string;
}>;
}
async function api<T = any>(endpoint: string, options: RequestInit = {}): Promise<T> {
const res = await fetch(`${API_URL}${endpoint}`, {
headers: { 'Content-Type': 'application/json', ...(options.headers as Record<string, string>) },
...options,
});
if (!res.ok) {
const body = await res.json().catch(() => null);
const msg = body?.error?.message || `HTTP ${res.status}`;
throw new Error(msg);
}
return res.json();
}
async function registerDevice() {
const venueId = args[1];
const name = args[2] || 'Simulated Terminal';
const code = args[3];
if (!venueId || !code) {
console.error('Usage: npx ts-node scripts/device-sync.ts register <venueId> <name> <registrationCode>');
process.exit(1);
}
console.log(`Registering device "${name}" in venue ${venueId}...`);
const result = await api('/devices/register', {
method: 'POST',
body: JSON.stringify({ venueId, name, registrationCode: code }),
});
console.log('Device registered!');
console.log(` Device ID: ${result.deviceId}`);
console.log(` Token: ${result.token}`);
console.log(` Expires: ${result.tokenExpiresAt}`);
console.log('\nSave this token for future sync operations.');
return result;
}
async function syncDevice() {
const deviceId = args[1];
const token = args[2];
if (!deviceId || !token) {
console.error('Usage: npx ts-node scripts/device-sync.ts sync <deviceId> <token> [cursor]');
process.exit(1);
}
const cursor = args[3];
const tabs = [
{ id: 'tab-' + Date.now(), customerName: 'Simulated Customer', version: 1, totalInCents: 1500, itemCount: 2 },
];
console.log(`Syncing device ${deviceId}...`);
const result = await api<SyncResult>(`/devices/${deviceId}/sync`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ deviceId, cursor: cursor || null, openTabs: tabs }),
});
console.log('Sync complete!');
console.log(` New cursor: ${result.cursor}`);
console.log(` Commands received: ${result.commands.length}`);
for (const cmd of result.commands) {
console.log(` Command: ${cmd.id} - ${cmd.type} (tab: ${cmd.tabId}, payment: ${cmd.paymentMethod})`);
}
return result;
}
async function ackCommand() {
const deviceId = args[1];
const token = args[2];
const commandId = args[3];
const status = args[4] || 'completed';
const result = args[5] || 'Tab closed successfully';
if (!deviceId || !token || !commandId) {
console.error('Usage: npx ts-node scripts/device-sync.ts ack <deviceId> <token> <commandId> [status] [result]');
process.exit(1);
}
console.log(`Acknowledging command ${commandId} as ${status}...`);
const res = await api(`/devices/${deviceId}/commands/${commandId}/ack`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ commandId, status, result }),
});
console.log('Ack complete!');
console.log(` Result: ${JSON.stringify(res)}`);
}
// Interactive demo sequence
async function demo() {
const venueId = args[1];
const code = args[2];
if (!venueId || !code) {
console.error('Usage: npx ts-node scripts/device-sync.ts demo <venueId> <registrationCode>');
console.error('First, generate a registration code from the dashboard (Devices > Add Device).');
process.exit(1);
}
try {
// 1. Register
const reg = await registerDevice();
const deviceId = reg.deviceId;
const token = reg.token;
// 2. Sync with tabs
console.log('\n--- Sync 1: Upload tabs ---');
const sync1 = await syncDevice();
if (sync1.commands.length > 0) {
// 3. Ack commands
for (const cmd of sync1.commands) {
console.log(`\n--- Ack: ${cmd.id} ---`);
await ackCommand(deviceId, token, cmd.id, 'completed', `Tab ${cmd.tabId} closed successfully`);
}
// 4. Sync again to see no pending commands
console.log('\n--- Sync 2: Verify no pending commands ---');
await syncDevice();
}
console.log('\n✅ Demo complete! Your device is fully functional.');
} catch (err: any) {
console.error('❌ Demo failed:', err.message);
}
}
async function main() {
try {
switch (command) {
case 'register':
await registerDevice();
break;
case 'sync':
await syncDevice();
break;
case 'ack':
await ackCommand();
break;
case 'demo':
await demo();
break;
default:
console.log(`
KoolTab Device Simulator
========================
Commands:
register <venueId> <name> <registrationCode> Register a new device
sync <deviceId> <token> [cursor] Sync device state
ack <deviceId> <token> <commandId> [status] Acknowledge a command
demo <venueId> <registrationCode> Run full demo sequence
Examples:
npx ts-node scripts/device-sync.ts register <venueId> "Bar Terminal 1" ABC123
npx ts-node scripts/device-sync.ts sync <deviceId> <token>
npx ts-node scripts/device-sync.ts ack <deviceId> <token> <commandId> completed
npx ts-node scripts/device-sync.ts demo <venueId> ABC123
Status values: completed, failed, conflict, expired
`);
}
} catch (err: any) {
console.error('Error:', err.message);
process.exit(1);
}
}
main();