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