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.
- package/index.js +139 -34
- 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
|
-
|
|
4
|
+
// child_process used in openBrowser()
|
|
5
5
|
|
|
6
6
|
const API_URL = 'api.dltpays.com';
|
|
7
|
-
const VERSION = '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
|
-
|
|
123
|
+
const parsed = JSON.parse(body);
|
|
124
|
+
resolve(parsed);
|
|
123
125
|
} catch {
|
|
124
|
-
|
|
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
|
-
|
|
146
|
+
cmd = 'open';
|
|
147
|
+
args = [url];
|
|
140
148
|
} else if (platform === 'win32') {
|
|
141
|
-
|
|
149
|
+
cmd = 'cmd';
|
|
150
|
+
args = ['/c', 'start', '', url];
|
|
142
151
|
} else {
|
|
143
|
-
|
|
152
|
+
cmd = 'xdg-open';
|
|
153
|
+
args = [url];
|
|
144
154
|
}
|
|
145
155
|
|
|
146
|
-
|
|
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.
|
|
215
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
475
|
-
|
|
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
|
-
|
|
507
|
+
let registrationNumber = await askWithValidation(
|
|
482
508
|
`${c.cyan} Guernsey registration number: ${c.reset}`,
|
|
483
|
-
(n) => n.
|
|
484
|
-
'
|
|
509
|
+
(n) => /^\d{4,10}$/.test(n.trim()),
|
|
510
|
+
'Must be a valid numeric registration number (4-10 digits)'
|
|
485
511
|
);
|
|
486
512
|
|
|
487
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
506
|
-
|
|
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
|
-
|
|
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.
|
|
536
|
-
console.log(`\n${c.red} ✗ Error: ${result
|
|
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.
|
|
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/
|
|
27
|
+
"url": "git+https://github.com/TokenCanvasIO/soundbip-nextjs.git",
|
|
28
|
+
"directory": "cli"
|
|
28
29
|
},
|
|
29
30
|
"homepage": "https://soundbip.com"
|
|
30
31
|
}
|