soundbip 2.1.2 → 2.3.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 +139 -34
  2. package/package.json +3 -2
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.3.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 = {
@@ -394,9 +411,10 @@ async function main() {
394
411
  console.log(` ${c.white}5.${c.reset} Governed by the laws of ${c.bold}Guernsey${c.reset}`);
395
412
  console.log('');
396
413
  console.log(` ${c.dim}Full terms: ${c.cyan}https://soundbip.com/terms${c.reset}`);
414
+ console.log(` ${c.dim}Privacy policy: ${c.cyan}https://soundbip.com/privacy${c.reset}`);
397
415
  console.log('');
398
416
 
399
- const termsAccepted = await ask(`${c.cyan} Do you accept the Vendor Terms of Service? [y/n]: ${c.reset}`);
417
+ const termsAccepted = await ask(`${c.cyan} Do you accept the Vendor Terms & Privacy Policy? [y/n]: ${c.reset}`);
400
418
  if (termsAccepted.trim().toLowerCase() !== 'y' && termsAccepted.trim().toLowerCase() !== 'yes') {
401
419
  console.log(`\n${c.yellow} Registration cancelled. You must accept the terms to continue.${c.reset}\n`);
402
420
  rl.close();
@@ -406,26 +424,26 @@ async function main() {
406
424
 
407
425
  // Store details with validation
408
426
  section('🏪', 'Business Details');
409
- const storeName = await askWithValidation(
427
+ let storeName = await askWithValidation(
410
428
  `${c.cyan} Store name: ${c.reset}`,
411
429
  (name) => name.length >= 2,
412
430
  'Store name must be at least 2 characters'
413
431
  );
414
432
 
415
- const storeUrl = await askWithValidation(
433
+ let storeUrl = await askWithValidation(
416
434
  `${c.cyan} Website URL: ${c.reset}`,
417
435
  isValidUrl,
418
436
  'Please enter a valid URL (e.g. mystore.com or https://mystore.com)',
419
437
  normalizeUrl
420
438
  );
421
439
 
422
- const email = await askWithValidation(
440
+ let email = await askWithValidation(
423
441
  `${c.cyan} Email: ${c.reset}`,
424
442
  isValidEmail,
425
443
  'Please enter a valid email address'
426
444
  );
427
445
 
428
- const ownerName = await askWithValidation(
446
+ let ownerName = await askWithValidation(
429
447
  `${c.cyan} Owner / contact name: ${c.reset}`,
430
448
  (name) => name.length >= 2 && name.length <= 100,
431
449
  'Name must be 2-100 characters'
@@ -471,39 +489,118 @@ async function main() {
471
489
  console.log(`${c.dim} ${c.reset}${c.cyan}[${num.padStart(2)}]${c.reset} ${name}`);
472
490
  });
473
491
  console.log('');
474
- const parishChoice = await ask(`${c.cyan} Select parish [1-10]: ${c.reset}`);
475
- const addressCity = PARISHES[parishChoice.trim()] || parishChoice.trim();
492
+ let addressCity;
493
+ while (true) {
494
+ const parishChoice = await ask(`${c.cyan} Select parish [1-10]: ${c.reset}`);
495
+ const picked = PARISHES[parishChoice.trim()];
496
+ if (picked) {
497
+ addressCity = picked;
498
+ break;
499
+ }
500
+ console.log(`${c.red} ✗ Please enter a number between 1 and 10${c.reset}`);
501
+ }
476
502
 
477
503
  // Optional fields
478
504
  section('📝', 'Additional Details');
479
505
  console.log('');
480
506
 
481
- const registrationNumber = await askWithValidation(
507
+ let registrationNumber = await askWithValidation(
482
508
  `${c.cyan} Guernsey registration number: ${c.reset}`,
483
- (n) => n.length >= 3,
484
- 'Registration number is required'
509
+ (n) => /^\d{4,10}$/.test(n.trim()),
510
+ 'Must be a valid numeric registration number (4-10 digits)'
485
511
  );
486
512
 
487
- const description = await ask(`${c.cyan} Describe your business (optional, max 250 chars): ${c.reset}`);
513
+ let description = await ask(`${c.cyan} Describe your business (optional, max 250 chars): ${c.reset}`);
488
514
 
489
515
  // Referral code (optional)
490
516
  const referralCode = await ask(`${c.cyan} Referral code (optional): ${c.reset}`);
491
517
 
492
518
  let referred_by_store = null;
493
519
  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}`);
520
+ try {
521
+ const lookupResult = await apiCall('GET', `/store/lookup-referral/${encodeURIComponent(referralCode.trim())}`);
522
+ if (lookupResult.success) {
523
+ referred_by_store = lookupResult.store_id;
524
+ console.log(`${c.green} Referred by: ${lookupResult.store_name}${c.reset}`);
525
+ } else {
526
+ console.log(`${c.yellow} ⚠ Referral code not found - continuing without referral${c.reset}`);
527
+ }
528
+ } catch {
529
+ console.log(`${c.yellow} ⚠ Could not verify referral code - continuing without referral${c.reset}`);
500
530
  }
501
531
  }
502
532
 
503
533
  // Platform selection
504
534
  showPlatformMenu();
505
- const platformChoice = await ask(`${c.cyan} Select platform [1-7]: ${c.reset}`);
506
- const platform = PLATFORMS[platformChoice] || PLATFORMS['3'];
535
+ let platform;
536
+ while (true) {
537
+ const platformChoice = await ask(`${c.cyan} Select platform [1-7]: ${c.reset}`);
538
+ platform = PLATFORMS[platformChoice.trim()];
539
+ if (platform) break;
540
+ console.log(`${c.red} ✗ Please enter a number between 1 and 7${c.reset}`);
541
+ }
542
+
543
+ // Review before submitting — allow edits
544
+ let reviewDone = false;
545
+ while (!reviewDone) {
546
+ section('✅', 'Review Your Details');
547
+ console.log('');
548
+ info('[1] Business', storeName);
549
+ info('[2] Website', storeUrl);
550
+ info('[3] Email', email);
551
+ info('[4] Owner', ownerName);
552
+ info('[5] Address', cleanStreet);
553
+ info('[6] Parish', addressCity);
554
+ info('[7] Postcode', addressPostcode);
555
+ info('[8] Reg Number', registrationNumber.trim());
556
+ if (description.trim()) info('[9] Description', description.trim());
557
+ else info('[9] Description', `${c.dim}(none)${c.reset}`);
558
+ info(' Platform', platform.name);
559
+ if (referred_by_store) info(' Referral', '✓ Applied');
560
+ console.log('');
561
+
562
+ const confirmReg = await ask(`${c.cyan} Everything correct? [y] confirm, [1-9] edit a field, [n] cancel: ${c.reset}`);
563
+ const choice = confirmReg.trim().toLowerCase();
564
+
565
+ if (choice === 'y' || choice === 'yes') {
566
+ reviewDone = true;
567
+ } else if (choice === 'n' || choice === 'no') {
568
+ console.log(`\n${c.yellow} Registration cancelled. Run npx soundbip again to start over.${c.reset}\n`);
569
+ rl.close();
570
+ return;
571
+ } else if (choice === '1') {
572
+ storeName = await askWithValidation(`${c.cyan} New business name: ${c.reset}`, (n) => n.length >= 2, 'Must be at least 2 characters');
573
+ } else if (choice === '2') {
574
+ storeUrl = await askWithValidation(`${c.cyan} New website URL: ${c.reset}`, isValidUrl, 'Please enter a valid URL', normalizeUrl);
575
+ } else if (choice === '3') {
576
+ email = await askWithValidation(`${c.cyan} New email: ${c.reset}`, isValidEmail, 'Please enter a valid email address');
577
+ } else if (choice === '4') {
578
+ ownerName = await askWithValidation(`${c.cyan} New owner name: ${c.reset}`, (n) => n.length >= 2 && n.length <= 100, 'Name must be 2-100 characters');
579
+ } else if (choice === '5') {
580
+ cleanStreet = await askWithValidation(`${c.cyan} New address: ${c.reset}`, (s) => s.length >= 2, 'Address is required');
581
+ } else if (choice === '6') {
582
+ console.log('');
583
+ Object.entries(PARISHES).forEach(([num, name]) => {
584
+ console.log(`${c.dim} ${c.reset}${c.cyan}[${num.padStart(2)}]${c.reset} ${name}`);
585
+ });
586
+ console.log('');
587
+ while (true) {
588
+ const pc = await ask(`${c.cyan} Select parish [1-10]: ${c.reset}`);
589
+ const picked = PARISHES[pc.trim()];
590
+ if (picked) { addressCity = picked; break; }
591
+ console.log(`${c.red} ✗ Please enter a number between 1 and 10${c.reset}`);
592
+ }
593
+ } else if (choice === '7') {
594
+ addressPostcode = await askWithValidation(`${c.cyan} New postcode: ${c.reset}`, (pc) => /^GY\d{1,2}\s?\d[A-Z]{2}$/i.test(pc.trim()), 'Must be a valid Guernsey postcode', (pc) => pc.trim().toUpperCase());
595
+ } else if (choice === '8') {
596
+ registrationNumber = await askWithValidation(`${c.cyan} New registration number: ${c.reset}`, (n) => /^\d{4,10}$/.test(n.trim()), 'Must be a valid numeric registration number (4-10 digits)');
597
+ } else if (choice === '9') {
598
+ const newDesc = await ask(`${c.cyan} New description (max 250 chars): ${c.reset}`);
599
+ description = newDesc.slice(0, 250);
600
+ } else {
601
+ console.log(`${c.red} ✗ Enter y, n, or 1-9 to edit a field${c.reset}`);
602
+ }
603
+ }
507
604
 
508
605
  const spin = spinner('Registering your store...');
509
606
 
@@ -528,12 +625,20 @@ async function main() {
528
625
  registerBody.description = description.trim();
529
626
  }
530
627
 
531
- const result = await apiCall('POST', '/store/register', registerBody);
628
+ let result;
629
+ try {
630
+ result = await apiCall('POST', '/store/register', registerBody);
631
+ } catch (err) {
632
+ stopSpinner(spin);
633
+ console.log(`\n${c.red} ✗ ${err.message}${c.reset}\n`);
634
+ rl.close();
635
+ return;
636
+ }
532
637
 
533
638
  stopSpinner(spin);
534
639
 
535
- if (result.error) {
536
- console.log(`\n${c.red} ✗ Error: ${result.error}${c.reset}\n`);
640
+ if (!result || !result.store_id) {
641
+ console.log(`\n${c.red} ✗ Error: ${result?.error || 'Registration failed — unexpected server response'}${c.reset}\n`);
537
642
  rl.close();
538
643
  return;
539
644
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soundbip",
3
- "version": "2.1.2",
3
+ "version": "2.3.0",
4
4
  "description": "CLI for SoundBip POS - instant affiliate commissions on XRPL",
5
5
  "bin": {
6
6
  "soundbip": "./index.js"
@@ -24,7 +24,8 @@
24
24
  "license": "MIT",
25
25
  "repository": {
26
26
  "type": "git",
27
- "url": "git+https://github.com/TokenCanvasIO/yesallofus-cli.git"
27
+ "url": "git+https://github.com/TokenCanvasIO/soundbip-nextjs.git",
28
+ "directory": "cli"
28
29
  },
29
30
  "homepage": "https://soundbip.com"
30
31
  }