@involvex/syncstuff-cli 0.0.6 → 0.0.9
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/Agends.md +110 -0
- package/dist/cli.js +4561 -677
- package/package.json +5 -2
- package/src/cli/commands/debug.ts +1 -2
- package/src/cli/commands/device.ts +12 -13
- package/src/cli/commands/devices.ts +12 -13
- package/src/cli/commands/help.ts +15 -0
- package/src/cli/commands/listen.ts +142 -0
- package/src/cli/commands/login.ts +1 -1
- package/src/cli/commands/logout.ts +1 -1
- package/src/cli/commands/scan.ts +4 -4
- package/src/cli/commands/transfer.ts +1 -1
- package/src/cli/commands/version.ts +4 -4
- package/src/cli/commands/whoami.ts +1 -1
- package/src/cli/index.ts +7 -1
- package/src/utils/config.ts +1 -0
- package/src/utils/network.ts +124 -124
- package/src/utils/ui.ts +1 -1
- package/.eslintignore +0 -1
package/src/utils/network.ts
CHANGED
|
@@ -1,146 +1,82 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import {
|
|
2
|
+
SYNCSTUFF_PROTOCOL,
|
|
3
|
+
SYNCSTUFF_SERVICE_TYPE,
|
|
4
|
+
type LocalDevice,
|
|
5
|
+
type ServiceTxtRecord,
|
|
6
|
+
} from "@syncstuff/network-types";
|
|
7
|
+
import { Bonjour, Browser, Service } from "bonjour-service";
|
|
8
|
+
import { createSocket } from "dgram";
|
|
9
|
+
import { createRequire } from "module";
|
|
10
|
+
import { v4 as uuidv4 } from "uuid";
|
|
11
|
+
import { readConfig, writeConfig } from "./config.js";
|
|
12
|
+
|
|
13
|
+
export type { LocalDevice };
|
|
14
|
+
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
const packageJson = require("../../../package.json");
|
|
15
17
|
/**
|
|
16
|
-
* Network scanner for discovering SyncStuff devices on local network
|
|
17
|
-
* Uses UDP multicast to find devices
|
|
18
|
+
* Network scanner for discovering SyncStuff devices on the local network using mDNS.
|
|
18
19
|
*/
|
|
19
20
|
class NetworkScanner {
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* Get local IP addresses
|
|
24
|
-
*/
|
|
25
|
-
getLocalIPs(): string[] {
|
|
26
|
-
const interfaces = networkInterfaces();
|
|
27
|
-
const ips: string[] = [];
|
|
21
|
+
private bonjour: Bonjour;
|
|
22
|
+
private browser: Browser | null = null;
|
|
23
|
+
private ad: Service | null = null;
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!iface) continue;
|
|
32
|
-
|
|
33
|
-
for (const addr of iface) {
|
|
34
|
-
if (addr.family === "IPv4" && !addr.internal) {
|
|
35
|
-
ips.push(addr.address);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return ips;
|
|
25
|
+
constructor() {
|
|
26
|
+
this.bonjour = new Bonjour();
|
|
41
27
|
}
|
|
42
28
|
|
|
43
29
|
/**
|
|
44
|
-
* Scan the local network for SyncStuff devices
|
|
45
|
-
* Uses UDP broadcast to discover devices
|
|
30
|
+
* Scan the local network for SyncStuff devices using mDNS.
|
|
46
31
|
*/
|
|
47
|
-
async scan(timeout =
|
|
32
|
+
async scan(timeout = 5000): Promise<LocalDevice[]> {
|
|
48
33
|
return new Promise(resolve => {
|
|
34
|
+
if (this.browser) {
|
|
35
|
+
this.browser.stop();
|
|
36
|
+
}
|
|
49
37
|
const devices: LocalDevice[] = [];
|
|
50
38
|
const seenIds = new Set<string>();
|
|
51
39
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
version: data.version || "1.0.0",
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
} catch {
|
|
79
|
-
// Ignore non-JSON messages
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
this.socket.bind(0, () => {
|
|
84
|
-
this.socket?.setBroadcast(true);
|
|
85
|
-
|
|
86
|
-
// Send discovery broadcast
|
|
87
|
-
const discoveryMessage = JSON.stringify({
|
|
88
|
-
service: "syncstuff",
|
|
89
|
-
action: "discover",
|
|
90
|
-
timestamp: Date.now(),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Broadcast on local network
|
|
94
|
-
const localIPs = this.getLocalIPs();
|
|
95
|
-
for (const ip of localIPs) {
|
|
96
|
-
const parts = ip.split(".");
|
|
97
|
-
parts[3] = "255";
|
|
98
|
-
const broadcastAddr = parts.join(".");
|
|
99
|
-
|
|
100
|
-
this.socket?.send(
|
|
101
|
-
discoveryMessage,
|
|
102
|
-
0,
|
|
103
|
-
discoveryMessage.length,
|
|
104
|
-
SYNCSTUFF_PORT,
|
|
105
|
-
broadcastAddr,
|
|
106
|
-
);
|
|
40
|
+
this.browser = this.bonjour.find({
|
|
41
|
+
type: SYNCSTUFF_SERVICE_TYPE,
|
|
42
|
+
protocol: SYNCSTUFF_PROTOCOL,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.browser.on("up", (service: Service) => {
|
|
46
|
+
const txt = (service.txt || {}) as unknown as ServiceTxtRecord;
|
|
47
|
+
const deviceId = txt.deviceId;
|
|
48
|
+
|
|
49
|
+
if (deviceId && !seenIds.has(deviceId)) {
|
|
50
|
+
const addresses = service.addresses || [];
|
|
51
|
+
const ip = addresses.find(addr => addr.includes("."));
|
|
52
|
+
if (ip) {
|
|
53
|
+
seenIds.add(deviceId);
|
|
54
|
+
devices.push({
|
|
55
|
+
id: deviceId,
|
|
56
|
+
name: txt.deviceName || service.name,
|
|
57
|
+
platform: txt.platform || "unknown",
|
|
58
|
+
ip,
|
|
59
|
+
port: service.port,
|
|
60
|
+
version: txt.version || "1.0.0",
|
|
61
|
+
});
|
|
107
62
|
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
108
65
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
SYNCSTUFF_PORT,
|
|
115
|
-
"224.0.0.251",
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Cleanup after timeout
|
|
120
|
-
setTimeout(() => {
|
|
121
|
-
this.cleanup();
|
|
122
|
-
resolve(devices);
|
|
123
|
-
}, timeout);
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.error("Failed to start scanner:", error);
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
if (this.browser) {
|
|
68
|
+
this.browser.stop();
|
|
69
|
+
this.browser = null;
|
|
70
|
+
}
|
|
126
71
|
resolve(devices);
|
|
127
|
-
}
|
|
72
|
+
}, timeout);
|
|
128
73
|
});
|
|
129
74
|
}
|
|
130
75
|
|
|
131
|
-
private cleanup(): void {
|
|
132
|
-
if (this.socket) {
|
|
133
|
-
try {
|
|
134
|
-
this.socket.close();
|
|
135
|
-
} catch {
|
|
136
|
-
// Ignore close errors
|
|
137
|
-
}
|
|
138
|
-
this.socket = null;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
76
|
/**
|
|
143
|
-
* Send a message to a specific device
|
|
77
|
+
* Send a message to a specific device.
|
|
78
|
+
* Note: This still uses a direct UDP socket, which is fine for direct communication
|
|
79
|
+
* once a device's IP and port are known.
|
|
144
80
|
*/
|
|
145
81
|
async sendTo(ip: string, port: number, message: object): Promise<void> {
|
|
146
82
|
return new Promise((resolve, reject) => {
|
|
@@ -157,6 +93,70 @@ class NetworkScanner {
|
|
|
157
93
|
});
|
|
158
94
|
});
|
|
159
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Start advertising this device on the local network using mDNS.
|
|
99
|
+
*/
|
|
100
|
+
startAdvertising(deviceName: string, port: number, platform = "cli") {
|
|
101
|
+
if (this.ad) {
|
|
102
|
+
this.ad.stop?.(() => {
|
|
103
|
+
this.ad = null;
|
|
104
|
+
this.publish(deviceName, port, platform);
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
this.publish(deviceName, port, platform);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Stop advertising this device.
|
|
113
|
+
*/
|
|
114
|
+
stopAdvertising() {
|
|
115
|
+
if (this.ad) {
|
|
116
|
+
this.ad.stop?.(() => {
|
|
117
|
+
this.ad = null;
|
|
118
|
+
console.log("Stopped advertising.");
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Unpublish all services and destroy the bonjour instance.
|
|
125
|
+
*/
|
|
126
|
+
destroy() {
|
|
127
|
+
this.bonjour.unpublishAll(() => {
|
|
128
|
+
this.bonjour.destroy();
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private publish(deviceName: string, port: number, platform: string) {
|
|
133
|
+
const config = readConfig();
|
|
134
|
+
if (!config.deviceId) {
|
|
135
|
+
config.deviceId = uuidv4();
|
|
136
|
+
writeConfig(config);
|
|
137
|
+
}
|
|
138
|
+
const deviceId = config.deviceId;
|
|
139
|
+
const version = packageJson.version;
|
|
140
|
+
|
|
141
|
+
this.ad = this.bonjour.publish({
|
|
142
|
+
name: `${deviceName}-${deviceId.substring(0, 6)}`,
|
|
143
|
+
type: SYNCSTUFF_SERVICE_TYPE,
|
|
144
|
+
port,
|
|
145
|
+
protocol: SYNCSTUFF_PROTOCOL,
|
|
146
|
+
txt: {
|
|
147
|
+
deviceId,
|
|
148
|
+
deviceName,
|
|
149
|
+
platform,
|
|
150
|
+
version,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
console.log(`Advertising '${deviceName}' on the network...`);
|
|
154
|
+
}
|
|
160
155
|
}
|
|
161
156
|
|
|
162
157
|
export const networkScanner = new NetworkScanner();
|
|
158
|
+
|
|
159
|
+
// Graceful shutdown
|
|
160
|
+
process.on("exit", () => networkScanner.destroy());
|
|
161
|
+
process.on("SIGINT", () => process.exit());
|
|
162
|
+
process.on("SIGTERM", () => process.exit());
|
package/src/utils/ui.ts
CHANGED
|
@@ -55,7 +55,7 @@ export function createTable(data: string[][], headers?: string[]): string {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export function animateText(text: string, delay
|
|
58
|
+
export function animateText(text: string, delay = 50): Promise<void> {
|
|
59
59
|
return new Promise(resolve => {
|
|
60
60
|
let index = 0;
|
|
61
61
|
const interval = setInterval(() => {
|
package/.eslintignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
eslint.config.ts
|