batman
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* 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();
|
||||
Reference in New Issue
Block a user