@vex-chat/cli 0.1.3 → 0.1.4
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/package.json +2 -2
- package/src/vex-chat.js +312 -65
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vex-chat/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Terminal client for vex-chat.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"better-sqlite3": "11.10.0",
|
|
33
33
|
"msgpackr": "^1.11.9",
|
|
34
|
-
"@vex-chat/libvex": "^6.6.
|
|
34
|
+
"@vex-chat/libvex": "^6.6.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^25.6.0",
|
package/src/vex-chat.js
CHANGED
|
@@ -359,14 +359,207 @@ function httpFromApiUrl(raw) {
|
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
+
function normalizeAccountHost(host) {
|
|
363
|
+
return String(host ?? DEFAULT_HOST)
|
|
364
|
+
.trim()
|
|
365
|
+
.toLowerCase();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function normalizeAccountName(value) {
|
|
369
|
+
return String(value ?? "")
|
|
370
|
+
.trim()
|
|
371
|
+
.toLowerCase();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function accountKeyFor(ctx, username) {
|
|
375
|
+
return `${normalizeAccountName(username)}@${normalizeAccountHost(ctx.clientOptions.host)}`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function parseAccountSelector(ctx, value) {
|
|
379
|
+
const selector = normalizeAccountName(value);
|
|
380
|
+
const currentHost = normalizeAccountHost(ctx.clientOptions.host);
|
|
381
|
+
const at = selector.lastIndexOf("@");
|
|
382
|
+
if (at > 0) {
|
|
383
|
+
const host = selector.slice(at + 1);
|
|
384
|
+
return {
|
|
385
|
+
host,
|
|
386
|
+
hostMatches: host === currentHost,
|
|
387
|
+
key: selector,
|
|
388
|
+
scoped: true,
|
|
389
|
+
username: selector.slice(0, at),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
host: currentHost,
|
|
394
|
+
hostMatches: true,
|
|
395
|
+
key: `${selector}@${currentHost}`,
|
|
396
|
+
scoped: false,
|
|
397
|
+
username: selector,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function stripExpiredPendingApproval(account) {
|
|
402
|
+
const expiresAt = account?.pendingApproval?.expiresAt;
|
|
403
|
+
if (typeof expiresAt !== "string") return false;
|
|
404
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
405
|
+
if (Number.isFinite(expiresAtMs) && expiresAtMs > Date.now()) {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
delete account.pendingApproval;
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function normalizeStoredAccount(config, key, fallbackUsername) {
|
|
413
|
+
const account = config.accounts[key];
|
|
414
|
+
if (!account || typeof account !== "object") return false;
|
|
415
|
+
let changed = stripExpiredPendingApproval(account);
|
|
416
|
+
const username =
|
|
417
|
+
typeof account.username === "string" && account.username.trim()
|
|
418
|
+
? account.username.trim().toLowerCase()
|
|
419
|
+
: fallbackUsername;
|
|
420
|
+
if (account.username !== username) {
|
|
421
|
+
account.username = username;
|
|
422
|
+
changed = true;
|
|
423
|
+
}
|
|
424
|
+
return changed;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function removeUnusableAccount(config, key) {
|
|
428
|
+
const account = config.accounts[key];
|
|
429
|
+
if (!account || account.deviceID || account.pendingApproval) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
delete config.accounts[key];
|
|
433
|
+
if (config.lastUsername === key) {
|
|
434
|
+
delete config.lastUsername;
|
|
435
|
+
}
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function resolveAccountEntry(ctx, config, selector) {
|
|
440
|
+
const parsed = parseAccountSelector(ctx, selector);
|
|
441
|
+
let changed = false;
|
|
442
|
+
|
|
443
|
+
if (!parsed.username) {
|
|
444
|
+
return { ...parsed, account: null, changed };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const exact = config.accounts[parsed.key];
|
|
448
|
+
if (parsed.scoped) {
|
|
449
|
+
if (exact) {
|
|
450
|
+
changed =
|
|
451
|
+
normalizeStoredAccount(config, parsed.key, parsed.username) ||
|
|
452
|
+
changed;
|
|
453
|
+
if (removeUnusableAccount(config, parsed.key)) {
|
|
454
|
+
return { ...parsed, account: null, changed: true };
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
...parsed,
|
|
458
|
+
account: config.accounts[parsed.key],
|
|
459
|
+
changed,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return { ...parsed, account: null, changed };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const scopedKey = accountKeyFor(ctx, parsed.username);
|
|
466
|
+
const scoped = config.accounts[scopedKey];
|
|
467
|
+
if (scoped) {
|
|
468
|
+
changed =
|
|
469
|
+
normalizeStoredAccount(config, scopedKey, parsed.username) ||
|
|
470
|
+
changed;
|
|
471
|
+
if (removeUnusableAccount(config, scopedKey)) {
|
|
472
|
+
return {
|
|
473
|
+
...parsed,
|
|
474
|
+
account: null,
|
|
475
|
+
changed: true,
|
|
476
|
+
key: scopedKey,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const legacy = config.accounts[parsed.username];
|
|
480
|
+
if (legacy && stripExpiredPendingApproval(legacy)) {
|
|
481
|
+
delete config.accounts[parsed.username];
|
|
482
|
+
changed = true;
|
|
483
|
+
}
|
|
484
|
+
if (config.lastUsername === parsed.username) {
|
|
485
|
+
config.lastUsername = scopedKey;
|
|
486
|
+
changed = true;
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
...parsed,
|
|
490
|
+
account: config.accounts[scopedKey],
|
|
491
|
+
changed,
|
|
492
|
+
key: scopedKey,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const legacy = config.accounts[parsed.username];
|
|
497
|
+
if (!legacy) {
|
|
498
|
+
return { ...parsed, account: null, changed, key: scopedKey };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
stripExpiredPendingApproval(legacy);
|
|
502
|
+
if (!legacy.deviceID && !legacy.pendingApproval) {
|
|
503
|
+
delete config.accounts[parsed.username];
|
|
504
|
+
if (config.lastUsername === parsed.username) {
|
|
505
|
+
delete config.lastUsername;
|
|
506
|
+
}
|
|
507
|
+
return { ...parsed, account: null, changed: true, key: scopedKey };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
config.accounts[scopedKey] = {
|
|
511
|
+
...legacy,
|
|
512
|
+
username:
|
|
513
|
+
typeof legacy.username === "string" && legacy.username.trim()
|
|
514
|
+
? legacy.username.trim().toLowerCase()
|
|
515
|
+
: parsed.username,
|
|
516
|
+
};
|
|
517
|
+
delete config.accounts[parsed.username];
|
|
518
|
+
if (config.lastUsername === parsed.username) {
|
|
519
|
+
config.lastUsername = scopedKey;
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
...parsed,
|
|
523
|
+
account: config.accounts[scopedKey],
|
|
524
|
+
changed: true,
|
|
525
|
+
key: scopedKey,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async function writeConfigIfChanged(ctx, config, changed) {
|
|
530
|
+
if (changed) {
|
|
531
|
+
await writeConfig(ctx.configPath, config);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function assertAccountHostMatches(ctx, accountRef) {
|
|
536
|
+
if (!accountRef.scoped || accountRef.hostMatches) return;
|
|
537
|
+
const currentHost = normalizeAccountHost(ctx.clientOptions.host);
|
|
538
|
+
throw new Error(
|
|
539
|
+
`Local account ${accountRef.key} is for ${accountRef.host}; current host is ${currentHost}. Pass --host ${accountRef.host}.`,
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function deleteLocalAccount(ctx, config, username) {
|
|
544
|
+
const { key } = parseAccountSelector(ctx, username);
|
|
545
|
+
delete config.accounts[key];
|
|
546
|
+
if (config.lastUsername === key) {
|
|
547
|
+
delete config.lastUsername;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
362
551
|
async function register(ctx, args) {
|
|
363
|
-
const
|
|
552
|
+
const requestedUsername = args[0] ?? ctx.username;
|
|
364
553
|
const password = args[1] ?? ctx.password;
|
|
365
|
-
if (!
|
|
554
|
+
if (!requestedUsername) {
|
|
366
555
|
throw new Error("Usage: vex-chat register <username> [password]");
|
|
367
556
|
}
|
|
368
557
|
const config = await readConfig(ctx.configPath);
|
|
369
|
-
|
|
558
|
+
const accountRef = resolveAccountEntry(ctx, config, requestedUsername);
|
|
559
|
+
await writeConfigIfChanged(ctx, config, accountRef.changed);
|
|
560
|
+
assertAccountHostMatches(ctx, accountRef);
|
|
561
|
+
const { username } = accountRef;
|
|
562
|
+
if (accountRef.account) {
|
|
370
563
|
throw new Error(
|
|
371
564
|
`Local account already exists for ${username}. Use login or remove it from ${ctx.configPath}.`,
|
|
372
565
|
);
|
|
@@ -414,15 +607,17 @@ async function persistNewLocalAccount(
|
|
|
414
607
|
client,
|
|
415
608
|
deviceID = client.me.device().deviceID,
|
|
416
609
|
) {
|
|
417
|
-
|
|
610
|
+
const accountRef = parseAccountSelector(ctx, username);
|
|
611
|
+
const storedUsername = client.me.user().username ?? accountRef.username;
|
|
612
|
+
config.accounts[accountRef.key] = {
|
|
418
613
|
deviceID,
|
|
419
614
|
privateKey,
|
|
420
615
|
userID: client.me.user().userID,
|
|
421
|
-
username,
|
|
616
|
+
username: storedUsername,
|
|
422
617
|
};
|
|
423
|
-
config.lastUsername =
|
|
618
|
+
config.lastUsername = accountRef.key;
|
|
424
619
|
await writeConfig(ctx.configPath, config);
|
|
425
|
-
return config.accounts[
|
|
620
|
+
return { ...config.accounts[accountRef.key], accountKey: accountRef.key };
|
|
426
621
|
}
|
|
427
622
|
|
|
428
623
|
async function persistPendingLocalAccount(
|
|
@@ -432,8 +627,9 @@ async function persistPendingLocalAccount(
|
|
|
432
627
|
privateKey,
|
|
433
628
|
pending,
|
|
434
629
|
) {
|
|
435
|
-
const
|
|
436
|
-
config.accounts[
|
|
630
|
+
const accountRef = parseAccountSelector(ctx, username);
|
|
631
|
+
const previous = config.accounts[accountRef.key] ?? {};
|
|
632
|
+
config.accounts[accountRef.key] = {
|
|
437
633
|
...previous,
|
|
438
634
|
privateKey,
|
|
439
635
|
pendingApproval: {
|
|
@@ -442,38 +638,38 @@ async function persistPendingLocalAccount(
|
|
|
442
638
|
requestID: pending.requestID,
|
|
443
639
|
},
|
|
444
640
|
...(pending.userID ? { userID: pending.userID } : {}),
|
|
445
|
-
username,
|
|
641
|
+
username: accountRef.username,
|
|
446
642
|
};
|
|
447
|
-
config.lastUsername =
|
|
643
|
+
config.lastUsername = accountRef.key;
|
|
448
644
|
await writeConfig(ctx.configPath, config);
|
|
449
|
-
return config.accounts[
|
|
645
|
+
return { ...config.accounts[accountRef.key], accountKey: accountRef.key };
|
|
450
646
|
}
|
|
451
647
|
|
|
452
648
|
async function login(ctx, args) {
|
|
453
|
-
const
|
|
649
|
+
const requestedUsername = args[0] ?? ctx.username;
|
|
454
650
|
const password = args[1] ?? ctx.password;
|
|
455
|
-
if (!
|
|
651
|
+
if (!requestedUsername) {
|
|
456
652
|
throw new Error("Usage: vex-chat login <username> [password]");
|
|
457
653
|
}
|
|
654
|
+
const { username } = parseAccountSelector(ctx, requestedUsername);
|
|
458
655
|
if (!password) {
|
|
459
|
-
await loginWithDeviceApproval(ctx,
|
|
656
|
+
await loginWithDeviceApproval(ctx, requestedUsername);
|
|
460
657
|
return;
|
|
461
658
|
}
|
|
462
|
-
const { client, config } = await makeClient(ctx,
|
|
659
|
+
const { client, config } = await makeClient(ctx, requestedUsername);
|
|
463
660
|
attachDebugClientEvents(ctx, client, `login:${username}`);
|
|
464
661
|
try {
|
|
465
662
|
const loginResult = await client.login(username, password);
|
|
466
663
|
if (!loginResult.ok)
|
|
467
664
|
throw new Error(loginResult.error ?? "Login failed.");
|
|
468
665
|
await connectAndWait(client, ctx, `login:${username}`);
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
userID: client.me.user().userID,
|
|
666
|
+
await persistNewLocalAccount(
|
|
667
|
+
ctx,
|
|
668
|
+
config,
|
|
473
669
|
username,
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
670
|
+
client.getKeys().private,
|
|
671
|
+
client,
|
|
672
|
+
);
|
|
477
673
|
console.log(
|
|
478
674
|
`${color(ROOT_ACCENT, "logged in")} ${color(userAccent(client.me.user().userID), username)}`,
|
|
479
675
|
);
|
|
@@ -485,11 +681,14 @@ async function login(ctx, args) {
|
|
|
485
681
|
|
|
486
682
|
async function loginWithDeviceApproval(ctx, username) {
|
|
487
683
|
const config = await readConfig(ctx.configPath);
|
|
488
|
-
|
|
489
|
-
|
|
684
|
+
const accountRef = resolveAccountEntry(ctx, config, username);
|
|
685
|
+
await writeConfigIfChanged(ctx, config, accountRef.changed);
|
|
686
|
+
assertAccountHostMatches(ctx, accountRef);
|
|
687
|
+
if (accountRef.account) {
|
|
688
|
+
const { client } = await authenticate(ctx, accountRef.key);
|
|
490
689
|
try {
|
|
491
690
|
console.log(
|
|
492
|
-
`${color(ROOT_ACCENT, "using")} ${color(userAccent(client.me.user().userID), username)}`,
|
|
691
|
+
`${color(ROOT_ACCENT, "using")} ${color(userAccent(client.me.user().userID), accountRef.username)}`,
|
|
493
692
|
);
|
|
494
693
|
printWhoami(client);
|
|
495
694
|
} finally {
|
|
@@ -498,22 +697,27 @@ async function loginWithDeviceApproval(ctx, username) {
|
|
|
498
697
|
return;
|
|
499
698
|
}
|
|
500
699
|
|
|
700
|
+
const { username: accountUsername } = accountRef;
|
|
501
701
|
const privateKey = Client.generateSecretKey();
|
|
502
702
|
const client = await Client.create(privateKey, ctx.clientOptions);
|
|
503
|
-
attachDebugClientEvents(ctx, client, `login-request:${
|
|
703
|
+
attachDebugClientEvents(ctx, client, `login-request:${accountUsername}`);
|
|
504
704
|
try {
|
|
505
|
-
const [, registerErr] = await client.register(
|
|
705
|
+
const [, registerErr] = await client.register(accountUsername);
|
|
506
706
|
if (!registerErr) {
|
|
507
|
-
await connectAndWait(
|
|
707
|
+
await connectAndWait(
|
|
708
|
+
client,
|
|
709
|
+
ctx,
|
|
710
|
+
`login-request:${accountUsername}`,
|
|
711
|
+
);
|
|
508
712
|
await persistNewLocalAccount(
|
|
509
713
|
ctx,
|
|
510
714
|
config,
|
|
511
|
-
|
|
715
|
+
accountUsername,
|
|
512
716
|
privateKey,
|
|
513
717
|
client,
|
|
514
718
|
);
|
|
515
719
|
console.log(
|
|
516
|
-
`${color(ROOT_ACCENT, "registered")} ${color(userAccent(client.me.user().userID),
|
|
720
|
+
`${color(ROOT_ACCENT, "registered")} ${color(userAccent(client.me.user().userID), accountUsername)}`,
|
|
517
721
|
);
|
|
518
722
|
printWhoami(client);
|
|
519
723
|
return;
|
|
@@ -521,12 +725,19 @@ async function loginWithDeviceApproval(ctx, username) {
|
|
|
521
725
|
if (!isDeviceApprovalRequired(registerErr)) {
|
|
522
726
|
throw registerErr;
|
|
523
727
|
}
|
|
524
|
-
await waitForDeviceApproval(
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
728
|
+
await waitForDeviceApproval(
|
|
729
|
+
ctx,
|
|
730
|
+
client,
|
|
731
|
+
config,
|
|
732
|
+
accountUsername,
|
|
733
|
+
privateKey,
|
|
734
|
+
{
|
|
735
|
+
challenge: registerErr.challenge,
|
|
736
|
+
expiresAt: registerErr.expiresAt,
|
|
737
|
+
requestID: registerErr.requestID,
|
|
738
|
+
userID: registerErr.userID,
|
|
739
|
+
},
|
|
740
|
+
);
|
|
530
741
|
} finally {
|
|
531
742
|
await client.close().catch(() => {});
|
|
532
743
|
}
|
|
@@ -591,7 +802,7 @@ async function waitForDeviceApproval(
|
|
|
591
802
|
requestID: pending.requestID,
|
|
592
803
|
})
|
|
593
804
|
.catch(() => {});
|
|
594
|
-
|
|
805
|
+
deleteLocalAccount(ctx, config, username);
|
|
595
806
|
await writeConfig(ctx.configPath, config);
|
|
596
807
|
throw new Error("Device login cancelled.");
|
|
597
808
|
}
|
|
@@ -672,7 +883,7 @@ async function waitForDeviceApproval(
|
|
|
672
883
|
);
|
|
673
884
|
}
|
|
674
885
|
}
|
|
675
|
-
|
|
886
|
+
deleteLocalAccount(ctx, config, username);
|
|
676
887
|
await writeConfig(ctx.configPath, config);
|
|
677
888
|
throw new Error(`Device login ${current.status}.`);
|
|
678
889
|
}
|
|
@@ -812,18 +1023,21 @@ function formatDeviceRequestLine(request) {
|
|
|
812
1023
|
}
|
|
813
1024
|
|
|
814
1025
|
async function useAccount(ctx, args) {
|
|
815
|
-
const
|
|
1026
|
+
const requestedUsername = requireArg(args, 0, "username");
|
|
816
1027
|
const config = await readConfig(ctx.configPath);
|
|
817
|
-
|
|
1028
|
+
const accountRef = resolveAccountEntry(ctx, config, requestedUsername);
|
|
1029
|
+
await writeConfigIfChanged(ctx, config, accountRef.changed);
|
|
1030
|
+
assertAccountHostMatches(ctx, accountRef);
|
|
1031
|
+
if (!accountRef.account) {
|
|
818
1032
|
throw new Error(
|
|
819
|
-
`No local account for ${username}. Run vex auth register ${username} first.`,
|
|
1033
|
+
`No local account for ${accountRef.username}. Run vex auth register ${accountRef.username} first.`,
|
|
820
1034
|
);
|
|
821
1035
|
}
|
|
822
|
-
config.lastUsername =
|
|
1036
|
+
config.lastUsername = accountRef.key;
|
|
823
1037
|
await writeConfig(ctx.configPath, config);
|
|
824
|
-
const account = config.accounts[
|
|
1038
|
+
const account = config.accounts[accountRef.key];
|
|
825
1039
|
console.log(
|
|
826
|
-
`${color(ROOT_ACCENT, "using")} ${color(userAccent(account.userID),
|
|
1040
|
+
`${color(ROOT_ACCENT, "using")} ${color(userAccent(account.userID), accountRef.key)}`,
|
|
827
1041
|
);
|
|
828
1042
|
}
|
|
829
1043
|
|
|
@@ -890,14 +1104,14 @@ async function channelCommand(ctx, args) {
|
|
|
890
1104
|
await useChannel(ctx, args);
|
|
891
1105
|
return;
|
|
892
1106
|
}
|
|
893
|
-
await withReadyClient(ctx, args, async (client, rest) => {
|
|
1107
|
+
await withReadyClient(ctx, args, async (client, rest, meta) => {
|
|
894
1108
|
if (sub === "list" || sub === "ls") {
|
|
895
1109
|
const serverID = requireArg(rest, 0, "server id");
|
|
896
1110
|
printChannels(await client.channels.retrieve(serverID));
|
|
897
1111
|
return;
|
|
898
1112
|
}
|
|
899
1113
|
if (sub === "history") {
|
|
900
|
-
const accountState = accountUiState(meta.config, meta.account);
|
|
1114
|
+
const accountState = accountUiState(ctx, meta.config, meta.account);
|
|
901
1115
|
const channelID = rest[0] ?? accountState.lastChannel;
|
|
902
1116
|
if (!channelID)
|
|
903
1117
|
throw new Error(
|
|
@@ -976,7 +1190,7 @@ async function groupCommand(ctx, args) {
|
|
|
976
1190
|
|
|
977
1191
|
async function sendCommand(ctx, args) {
|
|
978
1192
|
await withReadyClient(ctx, args, async (client, rest, meta) => {
|
|
979
|
-
const accountState = accountUiState(meta.config, meta.account);
|
|
1193
|
+
const accountState = accountUiState(ctx, meta.config, meta.account);
|
|
980
1194
|
let channelID = rest[0];
|
|
981
1195
|
let messageParts = rest.slice(1);
|
|
982
1196
|
if (messageParts.length === 0 && accountState.lastChannel) {
|
|
@@ -1043,7 +1257,7 @@ async function createServerInChat(ctx, client, state, name, rl) {
|
|
|
1043
1257
|
|
|
1044
1258
|
async function createInviteInteractive(ctx, client, state, args, rl) {
|
|
1045
1259
|
const config = await readConfig(ctx.configPath);
|
|
1046
|
-
const accountState = accountUiState(config, state.account);
|
|
1260
|
+
const accountState = accountUiState(ctx, config, state.account);
|
|
1047
1261
|
let serverID =
|
|
1048
1262
|
state.target?.type === "channel" && state.target.serverID
|
|
1049
1263
|
? state.target.serverID
|
|
@@ -2140,7 +2354,7 @@ async function chat(ctx, args) {
|
|
|
2140
2354
|
username,
|
|
2141
2355
|
);
|
|
2142
2356
|
attachDebugClientEvents(ctx, client, `chat:${account.username}`);
|
|
2143
|
-
const accountState = accountUiState(config, account);
|
|
2357
|
+
const accountState = accountUiState(ctx, config, account);
|
|
2144
2358
|
const state = {
|
|
2145
2359
|
account,
|
|
2146
2360
|
avatarMarkers: new Map(),
|
|
@@ -2703,13 +2917,20 @@ async function withReadyClient(ctx, args, fn) {
|
|
|
2703
2917
|
|
|
2704
2918
|
async function authenticate(ctx, explicitUsername) {
|
|
2705
2919
|
const config = await readConfig(ctx.configPath);
|
|
2706
|
-
const
|
|
2920
|
+
const accountRef = resolveAccountEntry(
|
|
2921
|
+
ctx,
|
|
2922
|
+
config,
|
|
2923
|
+
explicitUsername ?? config.lastUsername,
|
|
2924
|
+
);
|
|
2925
|
+
await writeConfigIfChanged(ctx, config, accountRef.changed);
|
|
2926
|
+
assertAccountHostMatches(ctx, accountRef);
|
|
2927
|
+
const { username } = accountRef;
|
|
2707
2928
|
if (!username) {
|
|
2708
2929
|
throw new Error(
|
|
2709
2930
|
"No local account selected. Use --username or run register/login first.",
|
|
2710
2931
|
);
|
|
2711
2932
|
}
|
|
2712
|
-
const account =
|
|
2933
|
+
const account = accountRef.account;
|
|
2713
2934
|
if (!account) {
|
|
2714
2935
|
throw new Error(
|
|
2715
2936
|
`No local account for ${username}. Run register/login first.`,
|
|
@@ -2750,9 +2971,13 @@ async function authenticate(ctx, explicitUsername) {
|
|
|
2750
2971
|
}
|
|
2751
2972
|
account.userID = client.me.user().userID;
|
|
2752
2973
|
account.username = client.me.user().username ?? username;
|
|
2753
|
-
config.accounts[
|
|
2974
|
+
config.accounts[accountRef.key] = account;
|
|
2754
2975
|
await writeConfig(ctx.configPath, config);
|
|
2755
|
-
return {
|
|
2976
|
+
return {
|
|
2977
|
+
account: { ...account, accountKey: accountRef.key },
|
|
2978
|
+
client,
|
|
2979
|
+
config,
|
|
2980
|
+
};
|
|
2756
2981
|
}
|
|
2757
2982
|
|
|
2758
2983
|
async function resolveStoredDeviceID(ctx, client, account, username) {
|
|
@@ -2775,20 +3000,32 @@ async function resolveStoredDeviceID(ctx, client, account, username) {
|
|
|
2775
3000
|
|
|
2776
3001
|
async function authenticateOrRegister(ctx, explicitUsername) {
|
|
2777
3002
|
const config = await readConfig(ctx.configPath);
|
|
2778
|
-
const
|
|
2779
|
-
|
|
2780
|
-
|
|
3003
|
+
const accountRef = resolveAccountEntry(
|
|
3004
|
+
ctx,
|
|
3005
|
+
config,
|
|
3006
|
+
explicitUsername ?? config.lastUsername,
|
|
3007
|
+
);
|
|
3008
|
+
await writeConfigIfChanged(ctx, config, accountRef.changed);
|
|
3009
|
+
assertAccountHostMatches(ctx, accountRef);
|
|
3010
|
+
if (accountRef.account) {
|
|
3011
|
+
return authenticate(ctx, accountRef.key);
|
|
2781
3012
|
}
|
|
2782
3013
|
|
|
2783
3014
|
const rl = createInterface({ input, output });
|
|
2784
3015
|
try {
|
|
2785
3016
|
console.log("Welcome to vex.");
|
|
2786
|
-
const
|
|
3017
|
+
const enteredRaw = (
|
|
3018
|
+
accountRef.username || (await rl.question("username: "))
|
|
3019
|
+
)
|
|
2787
3020
|
.trim()
|
|
2788
3021
|
.toLowerCase();
|
|
3022
|
+
const enteredRef = resolveAccountEntry(ctx, config, enteredRaw);
|
|
3023
|
+
await writeConfigIfChanged(ctx, config, enteredRef.changed);
|
|
3024
|
+
assertAccountHostMatches(ctx, enteredRef);
|
|
3025
|
+
const entered = enteredRef.username;
|
|
2789
3026
|
if (!entered) throw new Error("username is required");
|
|
2790
|
-
if (
|
|
2791
|
-
return authenticate(ctx,
|
|
3027
|
+
if (enteredRef.account) {
|
|
3028
|
+
return authenticate(ctx, enteredRef.key);
|
|
2792
3029
|
}
|
|
2793
3030
|
const answer = (await rl.question(`register ${entered}? [Y/n] `))
|
|
2794
3031
|
.trim()
|
|
@@ -2834,7 +3071,10 @@ async function authenticateOrRegister(ctx, explicitUsername) {
|
|
|
2834
3071
|
|
|
2835
3072
|
async function makeClient(ctx, username) {
|
|
2836
3073
|
const config = await readConfig(ctx.configPath);
|
|
2837
|
-
const
|
|
3074
|
+
const accountRef = resolveAccountEntry(ctx, config, username);
|
|
3075
|
+
await writeConfigIfChanged(ctx, config, accountRef.changed);
|
|
3076
|
+
assertAccountHostMatches(ctx, accountRef);
|
|
3077
|
+
const account = accountRef.account;
|
|
2838
3078
|
const privateKey = account?.privateKey ?? Client.generateSecretKey();
|
|
2839
3079
|
const client = await Client.create(privateKey, ctx.clientOptions);
|
|
2840
3080
|
return { client, config };
|
|
@@ -3523,9 +3763,11 @@ function targetToAccountUi(target) {
|
|
|
3523
3763
|
return patch;
|
|
3524
3764
|
}
|
|
3525
3765
|
|
|
3526
|
-
function accountUiState(config, account) {
|
|
3766
|
+
function accountUiState(ctx, config, account) {
|
|
3527
3767
|
if (!account) return {};
|
|
3528
|
-
const key =
|
|
3768
|
+
const key =
|
|
3769
|
+
account.accountKey ??
|
|
3770
|
+
(account.username ? accountKeyFor(ctx, account.username) : null);
|
|
3529
3771
|
const stored = key ? config.accounts?.[key]?.ui : null;
|
|
3530
3772
|
if (!stored || typeof stored !== "object") return {};
|
|
3531
3773
|
return {
|
|
@@ -3543,9 +3785,14 @@ function accountUiState(config, account) {
|
|
|
3543
3785
|
|
|
3544
3786
|
async function saveAccountUiState(ctx, account, patch) {
|
|
3545
3787
|
const config = await readConfig(ctx.configPath);
|
|
3546
|
-
const key =
|
|
3788
|
+
const key =
|
|
3789
|
+
account?.accountKey ??
|
|
3790
|
+
(account?.username ? accountKeyFor(ctx, account.username) : null);
|
|
3547
3791
|
if (!key || !config.accounts[key]) return;
|
|
3548
|
-
const current = accountUiState(config,
|
|
3792
|
+
const current = accountUiState(ctx, config, {
|
|
3793
|
+
...config.accounts[key],
|
|
3794
|
+
accountKey: key,
|
|
3795
|
+
});
|
|
3549
3796
|
config.accounts[key] = {
|
|
3550
3797
|
...config.accounts[key],
|
|
3551
3798
|
ui: {
|