soundbip 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +92 -27
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  const readline = require('readline');
3
3
  const https = require('https');
4
- const { exec } = require('child_process');
4
+ // child_process used in openBrowser()
5
5
 
6
6
  const API_URL = 'api.dltpays.com';
7
- const VERSION = '2.1.2';
7
+ const VERSION = '2.2.0';
8
8
 
9
9
  // Colors
10
10
  const c = {
@@ -112,19 +112,25 @@ function apiCall(method, path, data = null) {
112
112
  port: 443,
113
113
  path: `/api/v1${path}`,
114
114
  method,
115
- headers: { 'Content-Type': 'application/json' }
115
+ headers: { 'Content-Type': 'application/json' },
116
+ timeout: 30000
116
117
  };
117
118
  const req = https.request(options, res => {
118
119
  let body = '';
119
120
  res.on('data', chunk => body += chunk);
120
121
  res.on('end', () => {
121
122
  try {
122
- resolve(JSON.parse(body));
123
+ const parsed = JSON.parse(body);
124
+ resolve(parsed);
123
125
  } catch {
124
- resolve(body);
126
+ reject(new Error(`Server returned invalid response (HTTP ${res.statusCode})`));
125
127
  }
126
128
  });
127
129
  });
130
+ req.on('timeout', () => {
131
+ req.destroy();
132
+ reject(new Error('Request timed out — check your internet connection'));
133
+ });
128
134
  req.on('error', reject);
129
135
  if (data) req.write(JSON.stringify(data));
130
136
  req.end();
@@ -132,18 +138,22 @@ function apiCall(method, path, data = null) {
132
138
  }
133
139
 
134
140
  function openBrowser(url) {
141
+ const { execFile } = require('child_process');
135
142
  const platform = process.platform;
136
- let command;
137
143
 
144
+ let cmd, args;
138
145
  if (platform === 'darwin') {
139
- command = `open "${url}"`;
146
+ cmd = 'open';
147
+ args = [url];
140
148
  } else if (platform === 'win32') {
141
- command = `start "" "${url}"`;
149
+ cmd = 'cmd';
150
+ args = ['/c', 'start', '', url];
142
151
  } else {
143
- command = `xdg-open "${url}"`;
152
+ cmd = 'xdg-open';
153
+ args = [url];
144
154
  }
145
155
 
146
- exec(command, (err) => {
156
+ execFile(cmd, args, (err) => {
147
157
  if (err) {
148
158
  console.log(`${c.dim} Could not open browser automatically.${c.reset}`);
149
159
  }
@@ -198,6 +208,10 @@ function info(label, value, indent = ' ') {
198
208
  }
199
209
 
200
210
  function spinner(text) {
211
+ if (!process.stdout.isTTY) {
212
+ console.log(` ${text}`);
213
+ return null;
214
+ }
201
215
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
202
216
  let i = 0;
203
217
  process.stdout.write(`\n${c.yellow} ${frames[0]} ${text}${c.reset}`);
@@ -210,9 +224,12 @@ function spinner(text) {
210
224
  }
211
225
 
212
226
  function stopSpinner(interval) {
227
+ if (!interval) return;
213
228
  clearInterval(interval);
214
- process.stdout.clearLine(0);
215
- process.stdout.cursorTo(0);
229
+ if (process.stdout.isTTY) {
230
+ process.stdout.clearLine(0);
231
+ process.stdout.cursorTo(0);
232
+ }
216
233
  }
217
234
 
218
235
  const PLATFORMS = {
@@ -471,8 +488,16 @@ async function main() {
471
488
  console.log(`${c.dim} ${c.reset}${c.cyan}[${num.padStart(2)}]${c.reset} ${name}`);
472
489
  });
473
490
  console.log('');
474
- const parishChoice = await ask(`${c.cyan} Select parish [1-10]: ${c.reset}`);
475
- const addressCity = PARISHES[parishChoice.trim()] || parishChoice.trim();
491
+ let addressCity;
492
+ while (true) {
493
+ const parishChoice = await ask(`${c.cyan} Select parish [1-10]: ${c.reset}`);
494
+ const picked = PARISHES[parishChoice.trim()];
495
+ if (picked) {
496
+ addressCity = picked;
497
+ break;
498
+ }
499
+ console.log(`${c.red} ✗ Please enter a number between 1 and 10${c.reset}`);
500
+ }
476
501
 
477
502
  // Optional fields
478
503
  section('📝', 'Additional Details');
@@ -480,8 +505,8 @@ async function main() {
480
505
 
481
506
  const registrationNumber = await askWithValidation(
482
507
  `${c.cyan} Guernsey registration number: ${c.reset}`,
483
- (n) => n.length >= 3,
484
- 'Registration number is required'
508
+ (n) => /^\d{4,10}$/.test(n.trim()),
509
+ 'Must be a valid numeric registration number (4-10 digits)'
485
510
  );
486
511
 
487
512
  const description = await ask(`${c.cyan} Describe your business (optional, max 250 chars): ${c.reset}`);
@@ -491,19 +516,51 @@ async function main() {
491
516
 
492
517
  let referred_by_store = null;
493
518
  if (referralCode.trim()) {
494
- const lookupResult = await apiCall('GET', `/store/lookup-referral/${referralCode.trim()}`);
495
- if (lookupResult.success) {
496
- referred_by_store = lookupResult.store_id;
497
- console.log(`${c.green} ✓ Referred by: ${lookupResult.store_name}${c.reset}`);
498
- } else {
499
- console.log(`${c.yellow} Referral code not found - continuing without referral${c.reset}`);
519
+ try {
520
+ const lookupResult = await apiCall('GET', `/store/lookup-referral/${encodeURIComponent(referralCode.trim())}`);
521
+ if (lookupResult.success) {
522
+ referred_by_store = lookupResult.store_id;
523
+ console.log(`${c.green} Referred by: ${lookupResult.store_name}${c.reset}`);
524
+ } else {
525
+ console.log(`${c.yellow} ⚠ Referral code not found - continuing without referral${c.reset}`);
526
+ }
527
+ } catch {
528
+ console.log(`${c.yellow} ⚠ Could not verify referral code - continuing without referral${c.reset}`);
500
529
  }
501
530
  }
502
531
 
503
532
  // Platform selection
504
533
  showPlatformMenu();
505
- const platformChoice = await ask(`${c.cyan} Select platform [1-7]: ${c.reset}`);
506
- const platform = PLATFORMS[platformChoice] || PLATFORMS['3'];
534
+ let platform;
535
+ while (true) {
536
+ const platformChoice = await ask(`${c.cyan} Select platform [1-7]: ${c.reset}`);
537
+ platform = PLATFORMS[platformChoice.trim()];
538
+ if (platform) break;
539
+ console.log(`${c.red} ✗ Please enter a number between 1 and 7${c.reset}`);
540
+ }
541
+
542
+ // Review before submitting
543
+ section('✅', 'Review Your Details');
544
+ console.log('');
545
+ info('Business', storeName);
546
+ info('Website', storeUrl);
547
+ info('Email', email);
548
+ info('Owner', ownerName);
549
+ info('Address', cleanStreet);
550
+ info('Parish', addressCity);
551
+ info('Postcode', addressPostcode);
552
+ info('Reg Number', registrationNumber.trim());
553
+ if (description.trim()) info('Description', description.trim());
554
+ info('Platform', platform.name);
555
+ if (referred_by_store) info('Referral', '✓ Applied');
556
+ console.log('');
557
+
558
+ const confirmReg = await ask(`${c.cyan} Everything correct? [y/n]: ${c.reset}`);
559
+ if (confirmReg.trim().toLowerCase() !== 'y' && confirmReg.trim().toLowerCase() !== 'yes') {
560
+ console.log(`\n${c.yellow} Registration cancelled. Run npx soundbip again to start over.${c.reset}\n`);
561
+ rl.close();
562
+ return;
563
+ }
507
564
 
508
565
  const spin = spinner('Registering your store...');
509
566
 
@@ -528,12 +585,20 @@ async function main() {
528
585
  registerBody.description = description.trim();
529
586
  }
530
587
 
531
- const result = await apiCall('POST', '/store/register', registerBody);
588
+ let result;
589
+ try {
590
+ result = await apiCall('POST', '/store/register', registerBody);
591
+ } catch (err) {
592
+ stopSpinner(spin);
593
+ console.log(`\n${c.red} ✗ ${err.message}${c.reset}\n`);
594
+ rl.close();
595
+ return;
596
+ }
532
597
 
533
598
  stopSpinner(spin);
534
599
 
535
- if (result.error) {
536
- console.log(`\n${c.red} ✗ Error: ${result.error}${c.reset}\n`);
600
+ if (!result || !result.store_id) {
601
+ console.log(`\n${c.red} ✗ Error: ${result?.error || 'Registration failed — unexpected server response'}${c.reset}\n`);
537
602
  rl.close();
538
603
  return;
539
604
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soundbip",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "CLI for SoundBip POS - instant affiliate commissions on XRPL",
5
5
  "bin": {
6
6
  "soundbip": "./index.js"