nervepay 1.2.6 ā 1.2.8
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/bin/nervepay-cli.js +50 -29
- package/package.json +1 -1
package/bin/nervepay-cli.js
CHANGED
|
@@ -360,33 +360,55 @@ async function deviceNodePairing(options) {
|
|
|
360
360
|
console.log(chalk.cyan('\nš” Connecting to gateway...'));
|
|
361
361
|
const WebSocket = (await import('ws')).default;
|
|
362
362
|
|
|
363
|
-
const ws = new WebSocket(wsUrl);
|
|
363
|
+
const ws = new WebSocket(wsUrl, { rejectUnauthorized: false });
|
|
364
364
|
const timeoutMs = parseInt(options.timeout, 10) * 1000;
|
|
365
365
|
|
|
366
|
+
// Pre-compute crypto values before entering the WebSocket flow
|
|
367
|
+
// (avoids async gaps in the message handler that cause race conditions)
|
|
368
|
+
const { decodeBase58 } = await import('../dist/utils/crypto.js');
|
|
369
|
+
const pubKeyBytes = decodeBase58(publicKeyBase58);
|
|
370
|
+
const pubKeyB64 = Buffer.from(pubKeyBytes).toString('base64');
|
|
371
|
+
|
|
366
372
|
const result = await new Promise((resolve, reject) => {
|
|
373
|
+
let settled = false;
|
|
367
374
|
let connectId = null;
|
|
368
375
|
let pairId = null;
|
|
369
376
|
let requestId = null;
|
|
377
|
+
let lastError = null;
|
|
378
|
+
|
|
379
|
+
function settle(fn, value) {
|
|
380
|
+
if (settled) return;
|
|
381
|
+
settled = true;
|
|
382
|
+
clearTimeout(timer);
|
|
383
|
+
fn(value);
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
const timer = setTimeout(() => {
|
|
371
387
|
ws.close();
|
|
372
|
-
reject
|
|
373
|
-
}, timeoutMs + 15000);
|
|
388
|
+
settle(reject, new Error(`Pairing approval timed out (waited ${options.timeout}s)`));
|
|
389
|
+
}, timeoutMs + 15000);
|
|
374
390
|
|
|
375
391
|
ws.on('error', (err) => {
|
|
376
|
-
|
|
377
|
-
reject(new Error(`WebSocket error: ${err.message}`));
|
|
392
|
+
settle(reject, new Error(`WebSocket error: ${err.message}`));
|
|
378
393
|
});
|
|
379
394
|
|
|
380
|
-
ws.on('close', () => {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
395
|
+
ws.on('close', (code, reason) => {
|
|
396
|
+
// Delay slightly to let any pending message handler finish
|
|
397
|
+
setTimeout(() => {
|
|
398
|
+
const reasonStr = reason?.toString() || '';
|
|
399
|
+
const detail = lastError
|
|
400
|
+
? lastError
|
|
401
|
+
: reasonStr
|
|
402
|
+
? `code=${code} reason=${reasonStr}`
|
|
403
|
+
: `code=${code}`;
|
|
404
|
+
settle(reject, new Error(`Gateway closed connection (${detail})`));
|
|
405
|
+
}, 100);
|
|
385
406
|
});
|
|
386
407
|
|
|
387
408
|
ws.on('message', async (data) => {
|
|
388
409
|
try {
|
|
389
|
-
const
|
|
410
|
+
const raw = data.toString();
|
|
411
|
+
const frame = JSON.parse(raw);
|
|
390
412
|
|
|
391
413
|
// Step 5: Receive connect.challenge ā sign nonce
|
|
392
414
|
if (frame.event === 'connect.challenge') {
|
|
@@ -397,11 +419,6 @@ async function deviceNodePairing(options) {
|
|
|
397
419
|
const signatureB64 = await signRawBytes(privateKey, nonceBytes);
|
|
398
420
|
const signedAt = Date.now();
|
|
399
421
|
|
|
400
|
-
// Encode public key as base64 for the gateway
|
|
401
|
-
const { decodeBase58 } = await import('../dist/utils/crypto.js');
|
|
402
|
-
const pubKeyBytes = decodeBase58(publicKeyBase58);
|
|
403
|
-
const pubKeyB64 = Buffer.from(pubKeyBytes).toString('base64');
|
|
404
|
-
|
|
405
422
|
// Step 6: Send connect request as operator with device identity
|
|
406
423
|
connectId = crypto.randomUUID();
|
|
407
424
|
const connectReq = {
|
|
@@ -413,7 +430,7 @@ async function deviceNodePairing(options) {
|
|
|
413
430
|
scopes: ['operator.read', 'operator.write'],
|
|
414
431
|
client: {
|
|
415
432
|
id: `nervepay-cli-${crypto.randomUUID().slice(0, 8)}`,
|
|
416
|
-
version: '1.2.
|
|
433
|
+
version: '1.2.6',
|
|
417
434
|
platform: 'cli',
|
|
418
435
|
mode: 'pairing',
|
|
419
436
|
},
|
|
@@ -448,9 +465,10 @@ async function deviceNodePairing(options) {
|
|
|
448
465
|
};
|
|
449
466
|
ws.send(JSON.stringify(pairReq));
|
|
450
467
|
} else {
|
|
451
|
-
|
|
468
|
+
lastError = `Connect rejected: ${frame.error || 'Unknown error'}`;
|
|
469
|
+
console.error(chalk.red(` ${lastError}`));
|
|
452
470
|
ws.close();
|
|
453
|
-
reject
|
|
471
|
+
settle(reject, new Error(lastError));
|
|
454
472
|
}
|
|
455
473
|
return;
|
|
456
474
|
}
|
|
@@ -461,12 +479,13 @@ async function deviceNodePairing(options) {
|
|
|
461
479
|
requestId = frame.payload?.requestId || '';
|
|
462
480
|
console.log(chalk.green('ā Pairing request submitted'));
|
|
463
481
|
console.log(chalk.cyan('\nā³ Waiting for gateway owner approval...'));
|
|
464
|
-
console.log(chalk.gray(` Approve with: openclaw devices approve`));
|
|
482
|
+
console.log(chalk.gray(` Approve with: openclaw devices approve ${requestId}`));
|
|
465
483
|
console.log(chalk.gray(` Timeout: ${options.timeout}s\n`));
|
|
466
484
|
} else {
|
|
467
|
-
|
|
485
|
+
lastError = `Pair request rejected: ${frame.error || 'Unknown error'}`;
|
|
486
|
+
console.error(chalk.red(` ${lastError}`));
|
|
468
487
|
ws.close();
|
|
469
|
-
reject
|
|
488
|
+
settle(reject, new Error(lastError));
|
|
470
489
|
}
|
|
471
490
|
return;
|
|
472
491
|
}
|
|
@@ -474,26 +493,28 @@ async function deviceNodePairing(options) {
|
|
|
474
493
|
// Step 8: node.pair.resolved event
|
|
475
494
|
if (frame.event === 'node.pair.resolved') {
|
|
476
495
|
const rid = frame.payload?.requestId;
|
|
477
|
-
if (rid && rid !== requestId) return;
|
|
496
|
+
if (rid && rid !== requestId) return;
|
|
478
497
|
|
|
479
498
|
const decision = frame.payload?.decision;
|
|
480
499
|
if (decision === 'approved') {
|
|
481
|
-
clearTimeout(timer);
|
|
482
500
|
const token = frame.payload?.token || '';
|
|
483
501
|
const nodeId = frame.payload?.nodeId || '';
|
|
484
502
|
ws.close();
|
|
485
|
-
resolve
|
|
503
|
+
settle(resolve, { token, nodeId, requestId });
|
|
486
504
|
} else if (decision === 'rejected') {
|
|
487
|
-
clearTimeout(timer);
|
|
488
505
|
ws.close();
|
|
489
|
-
reject
|
|
506
|
+
settle(reject, new Error('Pairing request was rejected by the gateway owner'));
|
|
490
507
|
} else {
|
|
491
|
-
clearTimeout(timer);
|
|
492
508
|
ws.close();
|
|
493
|
-
reject
|
|
509
|
+
settle(reject, new Error(`Unexpected pairing decision: ${decision}`));
|
|
494
510
|
}
|
|
495
511
|
return;
|
|
496
512
|
}
|
|
513
|
+
|
|
514
|
+
// Log unhandled frames for debugging
|
|
515
|
+
if (frame.type === 'response' || frame.error) {
|
|
516
|
+
console.log(chalk.gray(' [debug] Unhandled frame:'), JSON.stringify(frame).slice(0, 200));
|
|
517
|
+
}
|
|
497
518
|
} catch (err) {
|
|
498
519
|
// Ignore parse errors for non-JSON messages (ping, etc)
|
|
499
520
|
}
|