@vex-chat/cli 0.1.4 → 0.1.5

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/package.json +2 -2
  2. package/src/vex-chat.js +73 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vex-chat/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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.3"
34
+ "@vex-chat/libvex": "^6.7.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^25.6.0",
package/src/vex-chat.js CHANGED
@@ -548,6 +548,37 @@ function deleteLocalAccount(ctx, config, username) {
548
548
  }
549
549
  }
550
550
 
551
+ function formatRemovedDeviceLoginHint(ctx, username) {
552
+ const host = normalizeAccountHost(ctx.clientOptions.host);
553
+ return `Local device for ${username}@${host} is no longer on the account. Run vex auth login ${username} --host ${host} to add this machine as a new device.`;
554
+ }
555
+
556
+ function isMissingStoredDeviceError(err) {
557
+ if (err?.response?.status === 404) return true;
558
+ const message = err instanceof Error ? err.message : String(err ?? "");
559
+ return /\b(?:http|status(?: code)?)?\s*404\b|404[^\n]*not found|not found[^\n]*404/i.test(
560
+ message,
561
+ );
562
+ }
563
+
564
+ function isRemovedStoredDeviceError(err) {
565
+ return err?.name === "RemovedStoredDeviceError";
566
+ }
567
+
568
+ async function removeStoredDeviceAccount(ctx, config, accountRef) {
569
+ delete config.accounts[accountRef.key];
570
+ if (config.lastUsername === accountRef.key) {
571
+ delete config.lastUsername;
572
+ }
573
+ await writeConfig(ctx.configPath, config);
574
+ }
575
+
576
+ function removedStoredDeviceError(ctx, username) {
577
+ const err = new Error(formatRemovedDeviceLoginHint(ctx, username));
578
+ err.name = "RemovedStoredDeviceError";
579
+ return err;
580
+ }
581
+
551
582
  async function register(ctx, args) {
552
583
  const requestedUsername = args[0] ?? ctx.username;
553
584
  const password = args[1] ?? ctx.password;
@@ -685,16 +716,33 @@ async function loginWithDeviceApproval(ctx, username) {
685
716
  await writeConfigIfChanged(ctx, config, accountRef.changed);
686
717
  assertAccountHostMatches(ctx, accountRef);
687
718
  if (accountRef.account) {
688
- const { client } = await authenticate(ctx, accountRef.key);
719
+ let existing;
689
720
  try {
721
+ existing = await authenticate(ctx, accountRef.key);
722
+ } catch (err) {
723
+ if (!isRemovedStoredDeviceError(err)) {
724
+ throw err;
725
+ }
690
726
  console.log(
691
- `${color(ROOT_ACCENT, "using")} ${color(userAccent(client.me.user().userID), accountRef.username)}`,
727
+ color(
728
+ "yellow",
729
+ `local device removed; requesting approval for ${accountRef.username} as a new device`,
730
+ ),
692
731
  );
693
- printWhoami(client);
694
- } finally {
695
- await client.close().catch(() => {});
732
+ await removeStoredDeviceAccount(ctx, config, accountRef);
733
+ }
734
+ if (existing) {
735
+ const { client } = existing;
736
+ try {
737
+ console.log(
738
+ `${color(ROOT_ACCENT, "using")} ${color(userAccent(client.me.user().userID), accountRef.username)}`,
739
+ );
740
+ printWhoami(client);
741
+ } finally {
742
+ await client.close().catch(() => {});
743
+ }
744
+ return;
696
745
  }
697
- return;
698
746
  }
699
747
 
700
748
  const { username: accountUsername } = accountRef;
@@ -2957,6 +3005,11 @@ async function authenticate(ctx, explicitUsername) {
2957
3005
  account.pendingApproval,
2958
3006
  );
2959
3007
  }
3008
+ if (deviceErr && isMissingStoredDeviceError(deviceErr)) {
3009
+ await client.close().catch(() => {});
3010
+ await removeStoredDeviceAccount(ctx, config, accountRef);
3011
+ throw removedStoredDeviceError(ctx, username);
3012
+ }
2960
3013
  if (deviceErr && ctx.password) {
2961
3014
  const loginResult = await client.login(username, ctx.password);
2962
3015
  if (!loginResult.ok)
@@ -3008,7 +3061,20 @@ async function authenticateOrRegister(ctx, explicitUsername) {
3008
3061
  await writeConfigIfChanged(ctx, config, accountRef.changed);
3009
3062
  assertAccountHostMatches(ctx, accountRef);
3010
3063
  if (accountRef.account) {
3011
- return authenticate(ctx, accountRef.key);
3064
+ try {
3065
+ return await authenticate(ctx, accountRef.key);
3066
+ } catch (err) {
3067
+ if (!isRemovedStoredDeviceError(err)) {
3068
+ throw err;
3069
+ }
3070
+ await removeStoredDeviceAccount(ctx, config, accountRef);
3071
+ console.log(
3072
+ color(
3073
+ "yellow",
3074
+ `local device removed; setting up ${accountRef.username} as a new device`,
3075
+ ),
3076
+ );
3077
+ }
3012
3078
  }
3013
3079
 
3014
3080
  const rl = createInterface({ input, output });