/** * 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(endpoint: string, options: RequestInit = {}): Promise { const res = await fetch(`${API_URL}${endpoint}`, { headers: { 'Content-Type': 'application/json', ...(options.headers as Record) }, ...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 '); 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 [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(`/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 [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 '); 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 Register a new device sync [cursor] Sync device state ack [status] Acknowledge a command demo Run full demo sequence Examples: npx ts-node scripts/device-sync.ts register "Bar Terminal 1" ABC123 npx ts-node scripts/device-sync.ts sync npx ts-node scripts/device-sync.ts ack completed npx ts-node scripts/device-sync.ts demo ABC123 Status values: completed, failed, conflict, expired `); } } catch (err: any) { console.error('Error:', err.message); process.exit(1); } } main();