ghostwire 1.0.0 → 2.0.0
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/index.js +1 -1
- package/package.json +1 -1
- package/src/handler/client.js +97 -9
- package/src/handler/server.js +144 -38
package/bin/index.js
CHANGED
package/package.json
CHANGED
package/src/handler/client.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
3
5
|
import showBanner from '../lib/banner.js';
|
|
4
6
|
|
|
5
7
|
export default function startClient(address) {
|
|
6
|
-
// Input might be "0.tcp.ngrok.io:12345" OR "localhost:3000"
|
|
7
|
-
// We strip the "tcp://" prefix if the user copied it directly from Ngrok
|
|
8
8
|
const cleanAddress = address.replace('tcp://', '');
|
|
9
9
|
const parts = cleanAddress.split(':');
|
|
10
10
|
|
|
@@ -61,13 +61,25 @@ export default function startClient(address) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
if (input) {
|
|
64
|
+
// --- FILE SHARING (SIDECAR) ---
|
|
65
|
+
if (input.startsWith('/share ')) {
|
|
66
|
+
const filePath = input.split('/share ')[1]?.trim();
|
|
67
|
+
handleFileUpload(filePath, HOST, PORT);
|
|
68
|
+
}
|
|
69
|
+
// --- FILE DOWNLOADING (SIDECAR) ---
|
|
70
|
+
else if (input.startsWith('/download ')) {
|
|
71
|
+
const fileName = input.split('/download ')[1]?.trim();
|
|
72
|
+
handleFileDownload(fileName, HOST, PORT);
|
|
73
|
+
}
|
|
74
|
+
// --- NORMAL CHAT ---
|
|
75
|
+
else {
|
|
76
|
+
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true }).toLowerCase();
|
|
77
|
+
console.log(`${chalk.blue('┌──(')}${chalk.green.bold('YOU@ghostwire')}${chalk.blue(')-[')}${chalk.white(time)}${chalk.blue(']')}`);
|
|
78
|
+
console.log(`${chalk.blue('└─#')} ${chalk.white(input)}`);
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// Send to server
|
|
70
|
-
client.write(input);
|
|
80
|
+
// Send to server
|
|
81
|
+
client.write(input);
|
|
82
|
+
}
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
// Draw new prompt
|
|
@@ -82,4 +94,80 @@ export default function startClient(address) {
|
|
|
82
94
|
client.on('error', (err) => {
|
|
83
95
|
console.log(chalk.red(`\n❌ Error: ${err.message}`));
|
|
84
96
|
});
|
|
85
|
-
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// --- HELPER FUNCTIONS ---
|
|
100
|
+
|
|
101
|
+
function handleFileUpload(filePath, host, port) {
|
|
102
|
+
if (!filePath) {
|
|
103
|
+
console.log(chalk.red('Usage: /share <path_to_file>'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check if file exists
|
|
108
|
+
// Resolve path relative to current working directory of the user
|
|
109
|
+
const absolutePath = path.resolve(process.cwd(), filePath);
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(absolutePath)) {
|
|
112
|
+
console.log(chalk.red(`\n ❌ File not found: ${absolutePath}`));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const filename = path.basename(absolutePath);
|
|
117
|
+
console.log(chalk.cyan(`\n 🚀 Starting upload: ${filename} (Sidecar Socket)...`));
|
|
118
|
+
|
|
119
|
+
const sidecar = new net.Socket();
|
|
120
|
+
sidecar.connect(port, host, () => {
|
|
121
|
+
// Handshake
|
|
122
|
+
sidecar.write(`SIDECAR_UPLOAD|${filename}\n`);
|
|
123
|
+
|
|
124
|
+
const fileStream = fs.createReadStream(absolutePath);
|
|
125
|
+
fileStream.pipe(sidecar);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
sidecar.on('close', () => {
|
|
129
|
+
// console.log(chalk.green(`\n ✅ Upload finished: ${filename}`));
|
|
130
|
+
// We rely on server system message or just silent finish to not clutter too much
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
sidecar.on('error', (err) => {
|
|
134
|
+
console.log(chalk.red(`\n ❌ Upload failed: ${err.message}`));
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handleFileDownload(filename, host, port) {
|
|
139
|
+
if (!filename) {
|
|
140
|
+
console.log(chalk.red('Usage: /download <filename>'));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Ensure downloads folder
|
|
145
|
+
const dlDir = path.join(process.cwd(), 'downloads');
|
|
146
|
+
if (!fs.existsSync(dlDir)) {
|
|
147
|
+
fs.mkdirSync(dlDir, { recursive: true });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const destPath = path.join(dlDir, filename);
|
|
151
|
+
console.log(chalk.cyan(`\n 📥 requesting download: ${filename}...`));
|
|
152
|
+
|
|
153
|
+
const sidecar = new net.Socket();
|
|
154
|
+
sidecar.connect(port, host, () => {
|
|
155
|
+
sidecar.write(`SIDECAR_DOWNLOAD|${filename}\n`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const fileStream = fs.createWriteStream(destPath);
|
|
159
|
+
sidecar.pipe(fileStream);
|
|
160
|
+
|
|
161
|
+
fileStream.on('finish', () => {
|
|
162
|
+
console.log(chalk.green(`\n ✅ File saved to: ${destPath}`));
|
|
163
|
+
sidecar.end();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
sidecar.on('error', (err) => {
|
|
167
|
+
console.log(chalk.red(`\n ❌ Sidecar Error: ${err.message}`));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
fileStream.on('error', (err) => {
|
|
171
|
+
console.log(chalk.red(`\n ❌ File Write Error: ${err.message}`));
|
|
172
|
+
});
|
|
173
|
+
}
|
package/src/handler/server.js
CHANGED
|
@@ -1,67 +1,173 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import SendMessage from '../lib/sendMessage.js';
|
|
3
|
-
import express from 'express';
|
|
4
3
|
import chalk from 'chalk';
|
|
5
4
|
import fs from 'fs';
|
|
6
5
|
import path from 'path';
|
|
7
6
|
import state from '../lib/state.js';
|
|
8
7
|
import showBanner from '../lib/banner.js';
|
|
9
8
|
|
|
9
|
+
// Ensure server storage exists
|
|
10
|
+
const SERVER_STORAGE = path.join(process.cwd(), 'server_storage');
|
|
11
|
+
if (!fs.existsSync(SERVER_STORAGE)) {
|
|
12
|
+
fs.mkdirSync(SERVER_STORAGE, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
export default function startServer() {
|
|
11
|
-
const
|
|
12
|
-
|
|
16
|
+
const PORT = 3000;
|
|
17
|
+
|
|
13
18
|
const server = net.createServer((socket) => {
|
|
14
|
-
|
|
15
|
-
socket.
|
|
16
|
-
|
|
19
|
+
// We listen for the first packet to determine connection type
|
|
20
|
+
socket.once('data', (initialData) => {
|
|
21
|
+
// Find newline delimiter
|
|
22
|
+
const newlineIndex = initialData.indexOf(10); // 0x0A is \n
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
let headerStr = '';
|
|
25
|
+
let remainingData = null;
|
|
26
|
+
|
|
27
|
+
if (newlineIndex !== -1) {
|
|
28
|
+
headerStr = initialData.subarray(0, newlineIndex).toString().trim();
|
|
29
|
+
remainingData = initialData.subarray(newlineIndex + 1);
|
|
30
|
+
} else {
|
|
31
|
+
headerStr = initialData.toString().trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- FILE UPLOAD SIDECAR ---
|
|
35
|
+
// Protocol: SIDECAR_UPLOAD|<filename>\n
|
|
36
|
+
if (headerStr.startsWith('SIDECAR_UPLOAD|')) {
|
|
37
|
+
const parts = headerStr.split('|');
|
|
38
|
+
const filename = parts[1];
|
|
39
|
+
if (!filename) {
|
|
40
|
+
socket.end();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
21
43
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!msg) return;
|
|
44
|
+
const safeFilename = path.basename(filename);
|
|
45
|
+
console.log(chalk.gray(`[FILE] Incoming upload: ${safeFilename}`));
|
|
25
46
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
const writeStream = fs.createWriteStream(path.join(SERVER_STORAGE, safeFilename));
|
|
48
|
+
|
|
49
|
+
// Write the remaining validation data if any exists in the first chunk
|
|
50
|
+
if (remainingData && remainingData.length > 0) {
|
|
51
|
+
writeStream.write(remainingData);
|
|
52
|
+
}
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
socket.write(`${chalk.gray(`[${time}]`)} ${chalk.yellow.bold('⚡ SYSTEM:')} ${chalk.white('Name set to')} ${chalk.cyan.bold(socket.name)}\n`);
|
|
54
|
+
socket.pipe(writeStream);
|
|
32
55
|
|
|
33
|
-
|
|
56
|
+
writeStream.on('finish', () => {
|
|
57
|
+
console.log(chalk.green(`[FILE] Upload complete: ${safeFilename}`));
|
|
58
|
+
// Broadcast availability
|
|
59
|
+
SendMessage('SYSTEM', `New file shared: ${chalk.bold(safeFilename)} (Type /download ${safeFilename})`);
|
|
60
|
+
socket.end();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
writeStream.on('error', (err) => {
|
|
64
|
+
console.error(chalk.red(`[FILE] Write error: ${err.message}`));
|
|
65
|
+
socket.end();
|
|
66
|
+
});
|
|
34
67
|
return;
|
|
35
68
|
}
|
|
36
69
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
// --- FILE DOWNLOAD SIDECAR ---
|
|
71
|
+
// Protocol: SIDECAR_DOWNLOAD|<filename>\n
|
|
72
|
+
if (headerStr.startsWith('SIDECAR_DOWNLOAD|')) {
|
|
73
|
+
const parts = headerStr.split('|');
|
|
74
|
+
const filename = parts[1];
|
|
75
|
+
if (!filename) {
|
|
76
|
+
socket.end();
|
|
77
|
+
return;
|
|
43
78
|
}
|
|
44
|
-
|
|
45
|
-
|
|
79
|
+
|
|
80
|
+
const safeFilename = path.basename(filename);
|
|
81
|
+
const filePath = path.join(SERVER_STORAGE, safeFilename);
|
|
82
|
+
|
|
83
|
+
console.log(chalk.gray(`[FILE] download request: ${safeFilename}`));
|
|
84
|
+
|
|
85
|
+
if (fs.existsSync(filePath)) {
|
|
86
|
+
const readStream = fs.createReadStream(filePath);
|
|
87
|
+
readStream.pipe(socket);
|
|
88
|
+
readStream.on('error', (err) => {
|
|
89
|
+
console.log(chalk.red('[FILE] Read error'));
|
|
90
|
+
});
|
|
91
|
+
// Socket ends when stream ends
|
|
92
|
+
} else {
|
|
93
|
+
console.log(chalk.red(`[FILE] Not found: ${safeFilename}`));
|
|
94
|
+
socket.write('ERROR_NOT_FOUND');
|
|
95
|
+
socket.end();
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
46
98
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
SendMessage('SYSTEM', `${socket.name} disconnected.`);
|
|
99
|
+
|
|
100
|
+
// --- STANDARD CHAT CLIENT ---
|
|
101
|
+
// Treat first packet as username/registration or just init
|
|
102
|
+
handleChatConnection(socket, initialData);
|
|
52
103
|
});
|
|
53
104
|
|
|
54
|
-
socket.on('error', () => {
|
|
55
|
-
|
|
56
|
-
SendMessage('SYSTEM', `${socket.name} disconnected.`);
|
|
105
|
+
socket.on('error', (err) => {
|
|
106
|
+
// Silent handle - cleanliness
|
|
57
107
|
});
|
|
58
108
|
});
|
|
59
|
-
|
|
109
|
+
|
|
110
|
+
server.listen(PORT, () => {
|
|
60
111
|
showBanner('GHOSTWIRE', 'HOST ACTIVE');
|
|
61
112
|
console.log(chalk.gray('--------------------------------------------------'));
|
|
62
|
-
console.log(chalk.green(` ✅ TCP Chat running on Port ${
|
|
113
|
+
console.log(chalk.green(` ✅ TCP Server (Chat + Files) running on Port ${PORT}`));
|
|
63
114
|
console.log(chalk.gray('--------------------------------------------------'));
|
|
64
115
|
console.log(chalk.yellow('\n ⚠ NEXT STEP: Start Ngrok ⚠'));
|
|
65
|
-
console.log(` To start ngrok, open new terminal and run: ${chalk.cyan.bold(
|
|
116
|
+
console.log(` To start ngrok, open new terminal and run: ${chalk.cyan.bold(`ngrok tcp ${PORT}`)}`);
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function handleChatConnection(socket, initialData) {
|
|
121
|
+
socket.name = `User-${state.sockets.length + 1}`;
|
|
122
|
+
socket.isRegistered = false;
|
|
123
|
+
state.sockets.push(socket);
|
|
124
|
+
|
|
125
|
+
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true }).toLowerCase();
|
|
126
|
+
socket.write(`${chalk.gray(`[${time}]`)} ${chalk.yellow.bold('⚡ SYSTEM:')} ${chalk.white(`Welcome to Ghostwire!`)}\n`);
|
|
127
|
+
|
|
128
|
+
// Process the initial data (likely the username request or empty)
|
|
129
|
+
processChatData(socket, initialData);
|
|
130
|
+
|
|
131
|
+
// Continue listening
|
|
132
|
+
socket.on('data', (data) => {
|
|
133
|
+
processChatData(socket, data);
|
|
66
134
|
});
|
|
67
|
-
|
|
135
|
+
|
|
136
|
+
socket.on('end', () => {
|
|
137
|
+
state.sockets = state.sockets.filter(s => s !== socket);
|
|
138
|
+
if (socket.isRegistered) {
|
|
139
|
+
SendMessage('SYSTEM', `${socket.name} disconnected.`);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
socket.on('error', () => {
|
|
144
|
+
state.sockets = state.sockets.filter(s => s !== socket);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function processChatData(socket, data) {
|
|
149
|
+
const msg = data.toString().trim();
|
|
150
|
+
if (!msg) return;
|
|
151
|
+
|
|
152
|
+
if (!socket.isRegistered) {
|
|
153
|
+
socket.name = msg;
|
|
154
|
+
socket.isRegistered = true;
|
|
155
|
+
|
|
156
|
+
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true }).toLowerCase();
|
|
157
|
+
socket.write(`${chalk.gray(`[${time}]`)} ${chalk.yellow.bold('⚡ SYSTEM:')} ${chalk.white('Name set to')} ${chalk.cyan.bold(socket.name)}\n`);
|
|
158
|
+
|
|
159
|
+
SendMessage('SYSTEM', `${socket.name} joined the chat`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (msg.startsWith('/nick ')) {
|
|
164
|
+
const name = msg.split(' ')[1];
|
|
165
|
+
if (name) {
|
|
166
|
+
socket.name = name;
|
|
167
|
+
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true }).toLowerCase();
|
|
168
|
+
socket.write(`${chalk.gray(`[${time}]`)} ${chalk.yellow.bold('⚡ SYSTEM:')} ${chalk.white('Name changed to')} ${chalk.cyan.bold(name)}\n`);
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
SendMessage(socket.name, msg, socket);
|
|
172
|
+
}
|
|
173
|
+
}
|