cli-setu 0.2.0 → 0.2.1
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/setu.js +6 -0
- package/package.json +1 -1
- package/src/pair.js +30 -2
- package/src/status.js +18 -0
- package/src/supervisor.js +13 -0
- package/src/unpair.js +65 -0
package/bin/setu.js
CHANGED
|
@@ -63,6 +63,11 @@ async function main() {
|
|
|
63
63
|
await stop();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
+
case 'unpair': {
|
|
67
|
+
const { unpair } = require('../src/unpair');
|
|
68
|
+
await unpair(AUTH_SERVER);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
66
71
|
default:
|
|
67
72
|
console.error(`[cli-setu] Unknown command: ${firstArg}`);
|
|
68
73
|
printHelp();
|
|
@@ -99,6 +104,7 @@ Usage:
|
|
|
99
104
|
cli-setu pair Pair this computer with your phone, nothing else
|
|
100
105
|
cli-setu status Show pairing status and any sessions running now
|
|
101
106
|
cli-setu stop Stop a running session
|
|
107
|
+
cli-setu unpair Unpair this computer (asks for confirmation)
|
|
102
108
|
cli-setu --version, -v Print the installed version
|
|
103
109
|
cli-setu --update Check for and install the latest version
|
|
104
110
|
cli-setu --help, -h Show this help`);
|
package/package.json
CHANGED
package/src/pair.js
CHANGED
|
@@ -11,8 +11,36 @@ async function pair(authServer, authServerPublic) {
|
|
|
11
11
|
const config = loadOrCreateConfig();
|
|
12
12
|
|
|
13
13
|
if (config.deviceToken) {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Real bug found by testing: unpairing from the phone deletes the
|
|
15
|
+
// device server-side, but the laptop's local config.json still has
|
|
16
|
+
// the old (now-dead) token - this used to trust that local file
|
|
17
|
+
// blindly and declare itself "already paired" even when the server
|
|
18
|
+
// had no record of it at all. Confirm the saved token is actually
|
|
19
|
+
// still valid before trusting it; a dead token (401 - the server
|
|
20
|
+
// genuinely doesn't recognize it) falls through to a real re-pair.
|
|
21
|
+
// A network error/unreachable server is a THIRD, distinct outcome -
|
|
22
|
+
// deliberately NOT treated as "unpaired," since that would force an
|
|
23
|
+
// unwanted re-pair on every flaky-wifi run; it proceeds as if still
|
|
24
|
+
// paired instead, same as this check not existing at all.
|
|
25
|
+
let serverSaysUnpaired = false;
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(`${authServer}/device/whoami`, {
|
|
28
|
+
headers: { Authorization: `Bearer ${config.deviceToken}` },
|
|
29
|
+
});
|
|
30
|
+
serverSaysUnpaired = res.status === 401;
|
|
31
|
+
} catch {
|
|
32
|
+
// Network error/unreachable - unknown, not "unpaired". Fall through
|
|
33
|
+
// to the "already paired" branch below, unchanged from before this
|
|
34
|
+
// check existed.
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!serverSaysUnpaired) {
|
|
38
|
+
console.log(`[cli-setu] "${config.deviceName}" is already paired.`);
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`[cli-setu] This device's pairing is no longer valid on the server (likely unpaired from the app) - re-pairing...`);
|
|
43
|
+
config.deviceToken = null;
|
|
16
44
|
}
|
|
17
45
|
|
|
18
46
|
console.log(`[cli-setu] Pairing as "${config.deviceName}"...`);
|
package/src/status.js
CHANGED
|
@@ -26,6 +26,24 @@ async function status(authServer) {
|
|
|
26
26
|
}
|
|
27
27
|
console.log(`[cli-setu] Paired as "${config.deviceName}".`);
|
|
28
28
|
|
|
29
|
+
// Real gap found by testing with multiple real accounts across multiple
|
|
30
|
+
// laptops: there was no way to confirm which Google account a laptop was
|
|
31
|
+
// paired to without checking the phone app directly - the device token
|
|
32
|
+
// itself carries no human-readable identity. Best-effort: a network
|
|
33
|
+
// failure here shouldn't block the rest of `status` from reporting what
|
|
34
|
+
// it already knows locally.
|
|
35
|
+
try {
|
|
36
|
+
const whoamiRes = await fetch(`${authServer}/device/whoami`, {
|
|
37
|
+
headers: { Authorization: `Bearer ${config.deviceToken}` },
|
|
38
|
+
});
|
|
39
|
+
if (whoamiRes.ok) {
|
|
40
|
+
const { userId } = await whoamiRes.json();
|
|
41
|
+
console.log(`[cli-setu] Paired to account: ${userId}`);
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// Offline, or the server's unreachable - status still works from local state alone.
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
let files;
|
|
30
48
|
try {
|
|
31
49
|
files = fs.readdirSync(CONFIG_DIR).filter((f) => f.startsWith('session.') && f.endsWith('.pid'));
|
package/src/supervisor.js
CHANGED
|
@@ -236,6 +236,19 @@ async function connectToAzure() {
|
|
|
236
236
|
log(`[supervisor] /device/connect rejected (403): ${err.error || 'no reason given'}`);
|
|
237
237
|
process.exit(1);
|
|
238
238
|
}
|
|
239
|
+
// Real bug found by testing: a 401 here means the server genuinely
|
|
240
|
+
// doesn't recognize this device token anymore (e.g. unpaired from the
|
|
241
|
+
// phone app) - this used to fall into the generic retry-every-3s path
|
|
242
|
+
// below, silently looping forever on a token that will never become
|
|
243
|
+
// valid again, with no indication to the user of what's actually
|
|
244
|
+
// wrong or what to do about it. Treated the same as 403 now: stop,
|
|
245
|
+
// say so clearly, point at the real fix.
|
|
246
|
+
if (res.status === 401) {
|
|
247
|
+
console.error(`\n[supervisor] This device is no longer paired (it may have been unpaired from the app).`);
|
|
248
|
+
console.error('[supervisor] Run `cli-setu pair` to pair it again.');
|
|
249
|
+
log(`[supervisor] /device/connect rejected (401): ${err.error || 'no reason given'}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
239
252
|
log(`[supervisor] /device/connect failed: ${err.error || res.status}`);
|
|
240
253
|
setTimeout(connectToAzure, 3000);
|
|
241
254
|
return;
|
package/src/unpair.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// `cli-setu unpair` - lets this laptop remove itself from its paired
|
|
2
|
+
// account, without needing the phone app. Real gap found by testing:
|
|
3
|
+
// unpairing only ever existed from the phone's Devices tab - there was no
|
|
4
|
+
// way to do this from the laptop side at all, and no way to recover from
|
|
5
|
+
// a stale local pairing except manually deleting ~/.cli-setu/config.json
|
|
6
|
+
// by hand.
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
const { readConfig, writeConfig } = require('./config');
|
|
9
|
+
|
|
10
|
+
function confirm(question) {
|
|
11
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
rl.question(question, (answer) => {
|
|
14
|
+
rl.close();
|
|
15
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function unpair(authServer) {
|
|
21
|
+
const config = readConfig();
|
|
22
|
+
if (!config || !config.deviceToken) {
|
|
23
|
+
console.log('[cli-setu] Not paired - nothing to unpair.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const confirmed = await confirm(
|
|
28
|
+
`[cli-setu] This will unpair "${config.deviceName}" and stop it from connecting. Continue? (y/N) `
|
|
29
|
+
);
|
|
30
|
+
if (!confirmed) {
|
|
31
|
+
console.log('[cli-setu] Cancelled.');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(`${authServer}/device/unpair-self`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { Authorization: `Bearer ${config.deviceToken}` },
|
|
39
|
+
});
|
|
40
|
+
// A 404 here means the server already has no record of this device
|
|
41
|
+
// (e.g. it was unpaired from the phone app already) - that's still a
|
|
42
|
+
// successful outcome from the user's point of view, so the local
|
|
43
|
+
// config is cleared the same way as a real 200.
|
|
44
|
+
if (!res.ok && res.status !== 404) {
|
|
45
|
+
const err = await res.json().catch(() => ({}));
|
|
46
|
+
console.error(`[cli-setu] Unpair failed: ${err.error || res.status}`);
|
|
47
|
+
console.error('[cli-setu] Local pairing left untouched - try again once the server is reachable.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error(`[cli-setu] Could not reach the server: ${err.message}`);
|
|
52
|
+
console.error('[cli-setu] Local pairing left untouched - try again once you\'re back online.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Only clear the local file once the server confirms - never unpair
|
|
57
|
+
// locally while leaving a real, still-active device row behind on the
|
|
58
|
+
// server (that would orphan it with no way to remove it via the CLI,
|
|
59
|
+
// since cli-setu pair would need a device token this file no longer has).
|
|
60
|
+
config.deviceToken = null;
|
|
61
|
+
writeConfig(config);
|
|
62
|
+
console.log(`[cli-setu] ✓ Unpaired. Run \`cli-setu pair\` to pair this computer again.`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { unpair };
|