9remote 2.0.1 → 2.0.7

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/lib/socketio.js DELETED
@@ -1,227 +0,0 @@
1
- // Main Socket.IO setup
2
- import { Server } from "socket.io";
3
- import { readFileSync } from "fs";
4
- import { join } from "path";
5
- import { homedir } from "os";
6
- import { setupTerminalSocket } from "../features/terminal/terminalSocket.js";
7
- import { setupRemoteSocket, checkRemoteAvailable } from "../features/remote/remoteSocket.js";
8
- import { setupFileExplorerSocket } from "../features/fileExplorer/fileExplorerSocket.js";
9
- import { trackConnection, untrackConnection, pushUiLog, clearOneTimeKey, pushUiEvent, setRemoteAvailable } from "../api/ui.js";
10
- import {
11
- loadApprovedDevices,
12
- isDeviceApproved,
13
- isDevicePending,
14
- approveDevice,
15
- addPendingApproval,
16
- removePendingApproval,
17
- getPendingApproval,
18
- markDeviceRejected,
19
- isDeviceRejected,
20
- updateRejectedSocket,
21
- clearRejectedDevice
22
- } from "./deviceApproval.js";
23
-
24
- function loadApiKey() {
25
- try {
26
- const keysFile = join(homedir(), ".9remote", "keys.json");
27
- const data = JSON.parse(readFileSync(keysFile, "utf8"));
28
- return data.key || null;
29
- } catch {
30
- return null;
31
- }
32
- }
33
-
34
- let ioInstance = null;
35
-
36
- export function getIO() {
37
- return ioInstance;
38
- }
39
-
40
- /** Setup features on an approved socket */
41
- function setupSocketFeatures(socket) {
42
- // Clear one-time key if used
43
- if (socket.handshake.auth?.tempKey) {
44
- pushUiLog("One-time key used \u2014 clearing from UI");
45
- clearOneTimeKey();
46
- }
47
- }
48
-
49
- /** Approve a pending socket by socketId */
50
- export function approveSocketDevice(socketId) {
51
- const io = ioInstance;
52
- if (!io) return false;
53
-
54
- const socket = io.sockets.sockets.get(socketId);
55
- const pending = getPendingApproval(socketId);
56
- console.log(`[DEBUG-APPROVE] socketId=${socketId}, socketExists=${!!socket}, pendingExists=${!!pending}`);
57
- if (!socket || !pending) return false;
58
-
59
- // Save device as approved; clear any prior rejection
60
- approveDevice(pending.deviceId);
61
- removePendingApproval(socketId);
62
- clearRejectedDevice(pending.deviceId);
63
-
64
- // Unlock socket + notify client
65
- socket.data.approved = true;
66
- console.log(`[DEBUG-APPROVE] Emitting device:approved to ${socketId}`);
67
- socket.emit("device:approved");
68
-
69
- // Setup features
70
- setupSocketFeatures(socket);
71
- pushUiLog(`Device approved: ${pending.deviceId.slice(0, 8)}...`);
72
-
73
- return true;
74
- }
75
-
76
- /** Approve a previously-rejected device by deviceId (from Clients list) */
77
- export function approveRejectedDevice(deviceId) {
78
- const io = ioInstance;
79
- if (!io || !deviceId) return false;
80
-
81
- approveDevice(deviceId);
82
- clearRejectedDevice(deviceId);
83
-
84
- // Notify any active socket for this device
85
- for (const socket of io.sockets.sockets.values()) {
86
- if (socket.handshake.auth?.deviceId === deviceId) {
87
- socket.data.approved = true;
88
- socket.emit("device:approved");
89
- setupSocketFeatures(socket);
90
- }
91
- }
92
- pushUiLog(`Device approved from pending: ${deviceId.slice(0, 8)}...`);
93
- return true;
94
- }
95
-
96
- /** Disconnect all active sockets belonging to a deviceId (device stays approved) */
97
- export function disconnectDeviceSockets(deviceId) {
98
- const io = ioInstance;
99
- if (!io || !deviceId) return 0;
100
- let count = 0;
101
- for (const socket of io.sockets.sockets.values()) {
102
- if (socket.handshake.auth?.deviceId === deviceId) {
103
- socket.disconnect(true);
104
- count++;
105
- }
106
- }
107
- if (count) pushUiLog(`Disconnected ${count} socket(s) for device ${deviceId.slice(0, 8)}...`);
108
- return count;
109
- }
110
-
111
- /** Reject a pending socket by socketId (remember deviceId in RAM as pending) */
112
- export function rejectSocketDevice(socketId) {
113
- const io = ioInstance;
114
- if (!io) return false;
115
-
116
- const pending = getPendingApproval(socketId);
117
- const socket = io.sockets.sockets.get(socketId);
118
- removePendingApproval(socketId);
119
-
120
- // Remember rejection in RAM so it shows up in Clients list as pending
121
- if (pending?.deviceId) {
122
- markDeviceRejected(pending.deviceId, { ip: pending.ip, socketId });
123
- }
124
-
125
- if (socket) {
126
- socket.emit("device:rejected");
127
- socket.disconnect(true);
128
- }
129
-
130
- // Notify UI to refresh pending/approved list
131
- pushUiEvent("deviceApproval", { action: "refresh" });
132
- pushUiLog(`Device rejected: ${pending?.deviceId?.slice(0, 8) || "unknown"}...`);
133
- return true;
134
- }
135
-
136
- export async function setupSocketIO(server) {
137
- // Load approved devices from disk
138
- loadApprovedDevices();
139
-
140
- const io = new Server(server, {
141
- cors: {
142
- origin: "*",
143
- methods: ["GET", "POST"],
144
- credentials: true,
145
- allowedHeaders: ["*"]
146
- },
147
- transports: ["websocket", "polling"],
148
- allowEIO3: true,
149
- allowUpgrades: true,
150
- pingTimeout: 60000,
151
- pingInterval: 25000,
152
- maxHttpBufferSize: 1e8
153
- });
154
-
155
- // Check remote availability at startup
156
- const hasRemote = await checkRemoteAvailable();
157
- setRemoteAvailable(hasRemote);
158
-
159
- // Track connections + device approval
160
- io.on("connection", (socket) => {
161
- const ip = socket.handshake.headers["x-forwarded-for"] || socket.handshake.address || "unknown";
162
- const deviceId = socket.handshake.auth?.deviceId || null;
163
-
164
- // Block all events from unapproved sockets (except device:clientReady)
165
- socket.data.approved = false;
166
- socket.use((packet, next) => {
167
- if (socket.data.approved) return next();
168
- const event = packet[0];
169
- if (event === "device:clientReady" || event === "disconnect") return next();
170
- return next(new Error("Device not approved"));
171
- });
172
-
173
- trackConnection(socket.id, ip, deviceId);
174
- pushUiLog(`Client connected: ${ip} (device: ${deviceId?.slice(0, 8) || "none"})`);
175
-
176
- socket.on("disconnect", (reason) => {
177
- untrackConnection(socket.id);
178
- removePendingApproval(socket.id);
179
- pushUiLog(`Client disconnected: ${ip} (${reason})`);
180
- });
181
-
182
- // Check device approval
183
- if (deviceId && isDeviceApproved(deviceId)) {
184
- // Known device — allow immediately
185
- pushUiLog(`Device recognized: ${deviceId.slice(0, 8)}...`);
186
- socket.data.approved = true;
187
- setupSocketFeatures(socket);
188
- } else if (deviceId && isDeviceRejected(deviceId)) {
189
- // Previously rejected — keep socket unapproved, no modal, update socketId for later approve
190
- updateRejectedSocket(deviceId, socket.id, ip);
191
- pushUiLog(`Rejected device reconnected: ${deviceId.slice(0, 8)} — waiting in Clients list`);
192
- socket.emit("device:rejected");
193
- pushUiEvent("deviceApproval", { action: "refresh" });
194
- } else {
195
- // Unknown device — hold and request approval
196
- pushUiLog(`Unknown device: ${deviceId?.slice(0, 8) || "no-id"} — waiting for approval`);
197
- // Skip if same deviceId already pending (client reconnected)
198
- if (isDevicePending(deviceId)) {
199
- pushUiLog(`Device ${deviceId?.slice(0, 8)} already pending, ignoring duplicate`);
200
- socket.disconnect(true);
201
- return;
202
- }
203
-
204
- addPendingApproval(socket.id, { deviceId, ip });
205
-
206
- // Wait for client to signal ready before emitting approval request
207
- socket.once("device:clientReady", () => {
208
- socket.emit("device:pendingApproval");
209
- pushUiEvent("deviceApproval", {
210
- socketId: socket.id,
211
- deviceId,
212
- ip,
213
- action: "pending"
214
- });
215
- });
216
- }
217
- });
218
-
219
- // Setup Terminal + Remote on same root namespace
220
- setupTerminalSocket(io, loadApiKey());
221
-
222
- // Setup File Explorer (uses default namespace)
223
- setupFileExplorerSocket(io);
224
-
225
- ioInstance = io;
226
- return io;
227
- }