topnic-https 0.0.5 → 0.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/bin/cli.js +54 -1
- package/package.json +7 -2
- package/readme +18 -0
- package/src/config/index.js +61 -0
- package/src/main/index.js +200 -47
- package/src/main/websocket.js +25 -0
- package/src/sharing/shareManager.js +66 -0
- package/src/utils/monitor.js +38 -0
package/bin/cli.js
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
#!/usr/bin/env node
|
1
|
+
#!/usr/bin/env node
|
2
2
|
|
3
3
|
const { program } = require('commander');
|
4
4
|
const server = require('../src/main/index.js');
|
5
|
+
const shareManager = require('../src/sharing/shareManager');
|
5
6
|
|
6
7
|
program
|
7
8
|
.version('1.0.0')
|
@@ -35,4 +36,56 @@ program
|
|
35
36
|
server.changePort(port);
|
36
37
|
});
|
37
38
|
|
39
|
+
program
|
40
|
+
.command('share')
|
41
|
+
.description('Share your local server temporarily')
|
42
|
+
.option('-p, --port <number>', 'Port to share', 3000)
|
43
|
+
.option('-d, --duration <minutes>', 'Share duration in minutes', 60)
|
44
|
+
.action(async (options) => {
|
45
|
+
try {
|
46
|
+
const share = await shareManager.createShare(options.port, options.duration);
|
47
|
+
console.log('\n📢 Share Information:');
|
48
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
49
|
+
console.log(`🔗 Public URL: ${share.url}`);
|
50
|
+
console.log(`🆔 Share ID: ${share.id}`);
|
51
|
+
console.log(`⏱️ Duration: ${options.duration} minutes`);
|
52
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
53
|
+
} catch (error) {
|
54
|
+
console.error('❌ Failed to create share:', error.message);
|
55
|
+
}
|
56
|
+
});
|
57
|
+
|
58
|
+
program
|
59
|
+
.command('shares')
|
60
|
+
.description('List all active shares')
|
61
|
+
.action(() => {
|
62
|
+
const shares = shareManager.getActiveShares();
|
63
|
+
if (shares.length === 0) {
|
64
|
+
console.log('📭 No active shares');
|
65
|
+
return;
|
66
|
+
}
|
67
|
+
|
68
|
+
console.log('\n📋 Active Shares:');
|
69
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
70
|
+
shares.forEach(share => {
|
71
|
+
console.log(`🔗 URL: ${share.url}`);
|
72
|
+
console.log(`🆔 ID: ${share.id}`);
|
73
|
+
console.log(`⏱️ Remaining: ${share.remainingMinutes} minutes`);
|
74
|
+
console.log('────────────────────────────────────────');
|
75
|
+
});
|
76
|
+
console.log('');
|
77
|
+
});
|
78
|
+
|
79
|
+
program
|
80
|
+
.command('unshare <shareId>')
|
81
|
+
.description('Stop sharing a specific server')
|
82
|
+
.action(async (shareId) => {
|
83
|
+
try {
|
84
|
+
await shareManager.closeShare(shareId);
|
85
|
+
console.log(`✅ Share ${shareId} has been stopped`);
|
86
|
+
} catch (error) {
|
87
|
+
console.error('❌ Failed to stop share:', error.message);
|
88
|
+
}
|
89
|
+
});
|
90
|
+
|
38
91
|
program.parse(process.argv);
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "topnic-https",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.7",
|
4
4
|
"main": "./src/main/index.js",
|
5
5
|
"scripts": {
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
@@ -87,9 +87,14 @@
|
|
87
87
|
"httpst": "./bin/cli.js"
|
88
88
|
},
|
89
89
|
"dependencies": {
|
90
|
+
"chokidar": "^4.0.1",
|
90
91
|
"commander": "^12.1.0",
|
92
|
+
"electron": "^33.2.0",
|
91
93
|
"fs": "^0.0.1-security",
|
92
94
|
"https": "^1.0.0",
|
93
|
-
"
|
95
|
+
"localtunnel": "^2.0.2",
|
96
|
+
"nanoid": "^5.0.8",
|
97
|
+
"path": "^0.12.7",
|
98
|
+
"ws": "^8.18.0"
|
94
99
|
}
|
95
100
|
}
|
package/readme
CHANGED
@@ -14,6 +14,24 @@ $ npm install -g topnic-https
|
|
14
14
|
- يدعم الخادم التوجيه HTTP و HTTPS
|
15
15
|
- يدعم الخادم المنطق البسيط
|
16
16
|
- يدعم الخادم التوجيه الأساسي
|
17
|
+
- يدعم الخادم الإختصارات المخصصة
|
18
|
+
- يدعم الخادم المشاركة في الويب لمده مؤقتة
|
19
|
+
|
20
|
+
## الإختصارات
|
21
|
+
- CommandOrControl+H+R - لتشغيل الخادم
|
22
|
+
- CommandOrControl+H+S - لإيقاف الخادم
|
23
|
+
- CommandOrControl+H+R+R - لإعادة تشغيل الخادم
|
24
|
+
- httpst share - لإنشاء مشاركة في الويب لمده مؤقتة
|
25
|
+
|
26
|
+
## أمثلة
|
27
|
+
```bash
|
28
|
+
$ httpst share -p 3000 -d 60 # مشاركة لمدة 60 دقيقة
|
29
|
+
$ httpst run #تشغيل الخادم
|
30
|
+
$ httpst stop #إيقاف الخادم
|
31
|
+
$ httpst restart #إعادة تشغيل الخادم
|
32
|
+
$ httpst change 3000 #تغيير بورت الخادم
|
33
|
+
```
|
34
|
+
- لإنشاء مشاركة في الويب لمده مؤقتة
|
17
35
|
|
18
36
|
# المطور
|
19
37
|
- ManKTrip
|
@@ -0,0 +1,61 @@
|
|
1
|
+
const fs = require('fs');
|
2
|
+
const path = require('path');
|
3
|
+
|
4
|
+
const defaultConfig = {
|
5
|
+
port: 3000,
|
6
|
+
ssl: {
|
7
|
+
enabled: true,
|
8
|
+
key: './ssl/key.pem',
|
9
|
+
cert: './ssl/cert.pem'
|
10
|
+
},
|
11
|
+
cors: {
|
12
|
+
enabled: true,
|
13
|
+
origin: '*',
|
14
|
+
methods: ['GET', 'POST', 'OPTIONS']
|
15
|
+
},
|
16
|
+
compression: {
|
17
|
+
enabled: true,
|
18
|
+
level: 6
|
19
|
+
},
|
20
|
+
cache: {
|
21
|
+
enabled: true,
|
22
|
+
maxAge: 31536000
|
23
|
+
},
|
24
|
+
hotReload: {
|
25
|
+
enabled: true,
|
26
|
+
ignore: ['.git', 'node_modules']
|
27
|
+
},
|
28
|
+
logging: {
|
29
|
+
enabled: true,
|
30
|
+
level: 'info'
|
31
|
+
},
|
32
|
+
shortcuts: {
|
33
|
+
enabled: true,
|
34
|
+
custom: {}
|
35
|
+
}
|
36
|
+
};
|
37
|
+
|
38
|
+
class Config {
|
39
|
+
static load(configPath) {
|
40
|
+
try {
|
41
|
+
const userConfig = fs.existsSync(configPath)
|
42
|
+
? JSON.parse(fs.readFileSync(configPath))
|
43
|
+
: {};
|
44
|
+
return { ...defaultConfig, ...userConfig };
|
45
|
+
} catch (error) {
|
46
|
+
Logger.error(`Error loading config: ${error.message}`);
|
47
|
+
return defaultConfig;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
static save(config, configPath) {
|
52
|
+
try {
|
53
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
54
|
+
Logger.success('Config saved successfully');
|
55
|
+
} catch (error) {
|
56
|
+
Logger.error(`Error saving config: ${error.message}`);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
module.exports = Config;
|
package/src/main/index.js
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
const https = require('https');
|
2
2
|
const fs = require('fs');
|
3
3
|
const path = require('path');
|
4
|
+
const zlib = require('zlib');
|
5
|
+
const { globalShortcut } = require('electron');
|
6
|
+
const server = require('../main/index.js');
|
4
7
|
|
5
8
|
let server;
|
6
9
|
let currentPort = 3000;
|
@@ -10,16 +13,28 @@ const defaultOptions = {
|
|
10
13
|
cert: fs.readFileSync(path.join(__dirname, '../ssl/cert.pem'))
|
11
14
|
};
|
12
15
|
|
16
|
+
const defaultCorsHeaders = {
|
17
|
+
'Access-Control-Allow-Origin': '*',
|
18
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
19
|
+
'Access-Control-Allow-Headers': 'Content-Type'
|
20
|
+
};
|
21
|
+
|
13
22
|
function serveStaticFile(req, res) {
|
23
|
+
Object.entries(defaultCorsHeaders).forEach(([key, value]) => {
|
24
|
+
res.setHeader(key, value);
|
25
|
+
});
|
26
|
+
|
14
27
|
let filePath = path.join(process.cwd(), req.url === '/' ? 'index.html' : req.url);
|
15
28
|
|
16
29
|
const extname = path.extname(filePath);
|
17
|
-
|
18
30
|
const contentTypes = {
|
19
31
|
'.html': 'text/html',
|
20
32
|
'.css': 'text/css',
|
21
|
-
'.py': 'text/x-python',
|
22
33
|
'.txt': 'text/plain',
|
34
|
+
'.json': 'application/json',
|
35
|
+
'.json5': 'application/json5',
|
36
|
+
'.jsonc': 'application/jsonc',
|
37
|
+
'.py': 'text/x-python',
|
23
38
|
'.c': 'text/x-csrc',
|
24
39
|
'.cpp': 'text/x-c++src',
|
25
40
|
'.cc': 'text/x-c++src',
|
@@ -27,34 +42,9 @@ function serveStaticFile(req, res) {
|
|
27
42
|
'.hpp': 'text/x-c++hdr',
|
28
43
|
'.hh': 'text/x-c++hdr',
|
29
44
|
'.js': 'text/javascript',
|
30
|
-
'.
|
31
|
-
'.
|
32
|
-
'.
|
33
|
-
'.gif': 'image/gif',
|
34
|
-
'.svg': 'image/svg+xml',
|
35
|
-
'.ico': 'image/x-icon',
|
36
|
-
'.webp': 'image/webp',
|
37
|
-
'.avif': 'image/avif',
|
38
|
-
'.mp4': 'video/mp4',
|
39
|
-
'.webm': 'video/webm',
|
40
|
-
'.ogg': 'video/ogg',
|
41
|
-
'.mp3': 'audio/mpeg',
|
42
|
-
'.wav': 'audio/wav',
|
43
|
-
'.flac': 'audio/flac',
|
44
|
-
'.pdf': 'application/pdf',
|
45
|
-
'.doc': 'application/msword',
|
46
|
-
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
47
|
-
'.xls': 'application/vnd.ms-excel',
|
48
|
-
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
49
|
-
'.ppt': 'application/vnd.ms-powerpoint',
|
50
|
-
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
51
|
-
'.zip': 'application/zip',
|
52
|
-
'.tar': 'application/x-tar',
|
53
|
-
'.gz': 'application/gzip',
|
54
|
-
'.bz2': 'application/x-bzip2',
|
55
|
-
'.xml': 'application/xml',
|
56
|
-
'.json': 'application/json',
|
57
|
-
'.wasm': 'application/wasm',
|
45
|
+
'.jsx': 'text/jsx',
|
46
|
+
'.ts': 'text/typescript',
|
47
|
+
'.tsx': 'text/tsx',
|
58
48
|
'.go': 'text/x-go',
|
59
49
|
'.rs': 'text/x-rustsrc',
|
60
50
|
'.env': 'text/plain',
|
@@ -62,13 +52,8 @@ function serveStaticFile(req, res) {
|
|
62
52
|
'.yml': 'text/yaml',
|
63
53
|
'.yaml': 'text/yaml',
|
64
54
|
'.toml': 'text/toml',
|
65
|
-
'.json5': 'application/json5',
|
66
|
-
'.jsonc': 'application/jsonc',
|
67
55
|
'.md': 'text/markdown',
|
68
56
|
'.mdx': 'text/markdown',
|
69
|
-
'.ts': 'text/typescript',
|
70
|
-
'.tsx': 'text/tsx',
|
71
|
-
'.jsx': 'text/jsx',
|
72
57
|
'.vue': 'text/x-vue',
|
73
58
|
'.svelte': 'text/x-svelte',
|
74
59
|
'.php': 'text/x-php',
|
@@ -85,17 +70,135 @@ function serveStaticFile(req, res) {
|
|
85
70
|
'.lua': 'text/x-lua',
|
86
71
|
'.pl': 'text/x-perl',
|
87
72
|
'.pm': 'text/x-perl',
|
88
|
-
'.py': 'text/x-python',
|
89
73
|
'.r': 'text/x-rsrc',
|
90
|
-
'.
|
91
|
-
'.
|
92
|
-
'.
|
93
|
-
'.
|
94
|
-
'.
|
95
|
-
'.
|
96
|
-
'.
|
74
|
+
'.xml': 'application/xml',
|
75
|
+
'.wasm': 'application/wasm',
|
76
|
+
'.png': 'image/png',
|
77
|
+
'.jpg': 'image/jpeg',
|
78
|
+
'.jpeg': 'image/jpeg',
|
79
|
+
'.gif': 'image/gif',
|
80
|
+
'.svg': 'image/svg+xml',
|
81
|
+
'.ico': 'image/x-icon',
|
82
|
+
'.webp': 'image/webp',
|
83
|
+
'.avif': 'image/avif',
|
84
|
+
'.mp4': 'video/mp4',
|
85
|
+
'.webm': 'video/webm',
|
86
|
+
'.ogg': 'video/ogg',
|
87
|
+
'.mp3': 'audio/mpeg',
|
88
|
+
'.wav': 'audio/wav',
|
89
|
+
'.flac': 'audio/flac',
|
90
|
+
'.pdf': 'application/pdf',
|
91
|
+
'.doc': 'application/msword',
|
92
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
93
|
+
'.xls': 'application/vnd.ms-excel',
|
94
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
95
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
96
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
97
|
+
'.zip': 'application/zip',
|
98
|
+
'.tar': 'application/x-tar',
|
99
|
+
'.gz': 'application/gzip',
|
100
|
+
'.bz2': 'application/x-bzip2',
|
101
|
+
'.7z': 'application/x-7z-compressed',
|
102
|
+
'.rar': 'application/x-rar-compressed',
|
103
|
+
'.xz': 'application/x-xz',
|
104
|
+
'.deb': 'application/x-debian-package',
|
105
|
+
'.iso': 'application/x-iso9660-image',
|
106
|
+
'.bin': 'application/octet-stream',
|
107
|
+
'.exe': 'application/x-msdownload',
|
108
|
+
'.msi': 'application/x-msi',
|
109
|
+
'.apk': 'application/vnd.android.package-archive',
|
110
|
+
'.dmg': 'application/x-apple-diskimage',
|
111
|
+
'.svgz': 'image/svg+xml',
|
112
|
+
'.tif': 'image/tiff',
|
113
|
+
'.tiff': 'image/tiff',
|
114
|
+
'.eps': 'application/postscript',
|
115
|
+
'.ps': 'application/postscript',
|
116
|
+
'.otf': 'font/otf',
|
117
|
+
'.ttf': 'font/ttf',
|
118
|
+
'.woff': 'font/woff',
|
119
|
+
'.woff2': 'font/woff2',
|
120
|
+
'.eot': 'application/vnd.ms-fontobject',
|
121
|
+
'.jsonld': 'application/ld+json',
|
122
|
+
'.crt': 'application/x-x509-ca-cert',
|
123
|
+
'.cer': 'application/pkix-cert',
|
124
|
+
'.pem': 'application/x-pem-file',
|
125
|
+
'.pfx': 'application/x-pkcs12',
|
126
|
+
'.key': 'application/x-pem-file',
|
127
|
+
'.ics': 'text/calendar',
|
128
|
+
'.csv': 'text/csv',
|
129
|
+
'.tsv': 'text/tab-separated-values',
|
130
|
+
'.webmanifest': 'application/manifest+json',
|
131
|
+
'.mpg': 'video/mpeg',
|
132
|
+
'.mpeg': 'video/mpeg',
|
133
|
+
'.mov': 'video/quicktime',
|
134
|
+
'.avi': 'video/x-msvideo',
|
135
|
+
'.mkv': 'video/x-matroska',
|
136
|
+
'.bat': 'application/x-bat',
|
137
|
+
'.ini': 'text/plain',
|
138
|
+
'.log': 'text/plain',
|
139
|
+
'.xsl': 'application/xml',
|
140
|
+
'.xsd': 'application/xml',
|
141
|
+
'.xhtml': 'application/xhtml+xml',
|
142
|
+
'.rss': 'application/rss+xml',
|
143
|
+
'.atom': 'application/atom+xml',
|
144
|
+
'.m3u8': 'application/x-mpegURL',
|
145
|
+
'.ts': 'video/mp2t',
|
146
|
+
'.jsonl': 'application/jsonl',
|
147
|
+
'.ndjson': 'application/x-ndjson',
|
148
|
+
'.geojson': 'application/geo+json',
|
149
|
+
'.3gp': 'video/3gpp',
|
150
|
+
'.3g2': 'video/3gpp2',
|
151
|
+
'.jar': 'application/java-archive',
|
152
|
+
'.war': 'application/java-archive',
|
153
|
+
'.ear': 'application/java-archive',
|
154
|
+
'.csv.gz': 'application/gzip',
|
155
|
+
'.stl': 'model/stl',
|
156
|
+
'.step': 'application/step',
|
157
|
+
'.iges': 'application/iges',
|
158
|
+
'.glb': 'model/gltf-binary',
|
159
|
+
'.gltf': 'model/gltf+json',
|
160
|
+
'.obj': 'model/obj',
|
161
|
+
'.fbx': 'model/fbx',
|
162
|
+
'.vrml': 'model/vrml',
|
163
|
+
'.dae': 'model/vnd.collada+xml',
|
164
|
+
'.3dm': 'model/x-3dmf',
|
165
|
+
'.blend': 'application/x-blender',
|
166
|
+
'.bak': 'application/octet-stream',
|
167
|
+
'.sql.gz': 'application/gzip',
|
168
|
+
'.ttc': 'font/collection',
|
169
|
+
'.cab': 'application/vnd.ms-cab-compressed',
|
170
|
+
'.lz': 'application/x-lzip',
|
171
|
+
'.lzma': 'application/x-lzma',
|
172
|
+
'.z': 'application/x-compress',
|
173
|
+
'.rpm': 'application/x-rpm',
|
174
|
+
'.cpio': 'application/x-cpio',
|
175
|
+
'.xz': 'application/x-xz',
|
176
|
+
'.img': 'application/octet-stream',
|
177
|
+
'.fits': 'application/fits',
|
178
|
+
'.parquet': 'application/octet-stream',
|
179
|
+
'.orc': 'application/octet-stream',
|
180
|
+
'.avro': 'application/octet-stream',
|
181
|
+
'.yaml.gz': 'application/gzip',
|
182
|
+
'.sql.lz': 'application/x-lzip',
|
183
|
+
'.sql.xz': 'application/x-xz',
|
184
|
+
'.json.gz': 'application/gzip',
|
185
|
+
'.json.xz': 'application/x-xz',
|
186
|
+
'.json5.gz': 'application/gzip',
|
187
|
+
'.json5.xz': 'application/x-xz',
|
188
|
+
'.jsonc.gz': 'application/gzip',
|
189
|
+
'.jsonc.xz': 'application/x-xz',
|
190
|
+
'.jsonl.gz': 'application/gzip',
|
191
|
+
'.jsonl.xz': 'application/x-xz',
|
192
|
+
'.ndjson.gz': 'application/gzip',
|
193
|
+
'.ndjson.xz': 'application/x-xz',
|
194
|
+
'.parquet.gz': 'application/gzip',
|
195
|
+
'.parquet.xz': 'application/x-xz',
|
196
|
+
'.orc.gz': 'application/gzip',
|
197
|
+
'.orc.xz': 'application/x-xz',
|
198
|
+
'.avro.gz': 'application/gzip',
|
199
|
+
'.avro.xz': 'application/x-xz'
|
97
200
|
};
|
98
|
-
|
201
|
+
|
99
202
|
const contentType = contentTypes[extname] || 'text/plain';
|
100
203
|
|
101
204
|
fs.readFile(filePath, (error, content) => {
|
@@ -108,10 +211,36 @@ function serveStaticFile(req, res) {
|
|
108
211
|
res.end('Server Error: ' + error.code);
|
109
212
|
}
|
110
213
|
} else {
|
111
|
-
|
112
|
-
|
214
|
+
const acceptEncoding = req.headers['accept-encoding'] || '';
|
215
|
+
|
216
|
+
if (acceptEncoding.includes('gzip')) {
|
217
|
+
res.writeHead(200, {
|
218
|
+
'Content-Type': contentType,
|
219
|
+
'Content-Encoding': 'gzip'
|
220
|
+
});
|
221
|
+
zlib.gzip(content, (_, result) => res.end(result));
|
222
|
+
} else if (acceptEncoding.includes('deflate')) {
|
223
|
+
res.writeHead(200, {
|
224
|
+
'Content-Type': contentType,
|
225
|
+
'Content-Encoding': 'deflate'
|
226
|
+
});
|
227
|
+
zlib.deflate(content, (_, result) => res.end(result));
|
228
|
+
} else {
|
229
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
230
|
+
res.end(content);
|
231
|
+
}
|
113
232
|
}
|
114
233
|
});
|
234
|
+
|
235
|
+
const cacheControl = {
|
236
|
+
'.html': 'no-cache',
|
237
|
+
'.css': 'public, max-age=31536000',
|
238
|
+
'.js': 'public, max-age=31536000',
|
239
|
+
'.png': 'public, max-age=31536000',
|
240
|
+
'.jpg': 'public, max-age=31536000'
|
241
|
+
};
|
242
|
+
|
243
|
+
res.setHeader('Cache-Control', cacheControl[extname] || 'no-cache');
|
115
244
|
}
|
116
245
|
|
117
246
|
function start() {
|
@@ -146,9 +275,33 @@ function changePort(newPort) {
|
|
146
275
|
console.log(`Port changed to ${currentPort}`);
|
147
276
|
}
|
148
277
|
|
278
|
+
function registerShortcuts() {
|
279
|
+
globalShortcut.register('CommandOrControl+Shift+H+R', () => {
|
280
|
+
console.log('🚀 Starting server via shortcut...');
|
281
|
+
server.start();
|
282
|
+
});
|
283
|
+
|
284
|
+
globalShortcut.register('CommandOrControl+Shift+H+S', () => {
|
285
|
+
console.log('🛑 Stopping server via shortcut...');
|
286
|
+
server.stop();
|
287
|
+
});
|
288
|
+
|
289
|
+
globalShortcut.register('CommandOrControl+Shift+H+R+R', () => {
|
290
|
+
console.log('🔄 Restarting server via shortcut...');
|
291
|
+
server.restart();
|
292
|
+
});
|
293
|
+
}
|
294
|
+
|
295
|
+
function unregisterShortcuts() {
|
296
|
+
globalShortcut.unregisterAll();
|
297
|
+
}
|
298
|
+
|
149
299
|
module.exports = {
|
150
300
|
start,
|
151
301
|
stop,
|
152
302
|
restart,
|
153
|
-
changePort
|
303
|
+
changePort,
|
304
|
+
configure,
|
305
|
+
registerShortcuts,
|
306
|
+
unregisterShortcuts
|
154
307
|
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
const WebSocket = require('ws');
|
2
|
+
|
3
|
+
function setupWebSocket(server) {
|
4
|
+
const wss = new WebSocket.Server({ server });
|
5
|
+
|
6
|
+
wss.on('connection', (ws) => {
|
7
|
+
console.log('🔌 New client connected');
|
8
|
+
|
9
|
+
ws.on('close', () => {
|
10
|
+
console.log('Client disconnected');
|
11
|
+
});
|
12
|
+
});
|
13
|
+
|
14
|
+
return wss;
|
15
|
+
}
|
16
|
+
|
17
|
+
function notifyClients(wss, message) {
|
18
|
+
wss.clients.forEach((client) => {
|
19
|
+
if (client.readyState === WebSocket.OPEN) {
|
20
|
+
client.send(JSON.stringify(message));
|
21
|
+
}
|
22
|
+
});
|
23
|
+
}
|
24
|
+
|
25
|
+
module.exports = { setupWebSocket, notifyClients };
|
@@ -0,0 +1,66 @@
|
|
1
|
+
const localtunnel = require('localtunnel');
|
2
|
+
const { nanoid } = require('nanoid');
|
3
|
+
const Logger = require('../utils/logger');
|
4
|
+
|
5
|
+
class ShareManager {
|
6
|
+
constructor() {
|
7
|
+
this.activeShares = new Map();
|
8
|
+
}
|
9
|
+
|
10
|
+
async createShare(port, duration) {
|
11
|
+
try {
|
12
|
+
const shareId = nanoid(8);
|
13
|
+
|
14
|
+
const tunnel = await localtunnel({ port });
|
15
|
+
|
16
|
+
const shareInfo = {
|
17
|
+
id: shareId,
|
18
|
+
url: tunnel.url,
|
19
|
+
startTime: Date.now(),
|
20
|
+
duration: duration * 60 * 1000,
|
21
|
+
tunnel
|
22
|
+
};
|
23
|
+
|
24
|
+
this.activeShares.set(shareId, shareInfo);
|
25
|
+
|
26
|
+
setTimeout(() => {
|
27
|
+
this.closeShare(shareId);
|
28
|
+
}, shareInfo.duration);
|
29
|
+
|
30
|
+
Logger.success(`🌍 Share created: ${tunnel.url}`);
|
31
|
+
Logger.info(`⏱️ Share will expire in ${duration} minutes`);
|
32
|
+
|
33
|
+
return shareInfo;
|
34
|
+
} catch (error) {
|
35
|
+
Logger.error(`Share creation failed: ${error.message}`);
|
36
|
+
throw error;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
async closeShare(shareId) {
|
41
|
+
const share = this.activeShares.get(shareId);
|
42
|
+
if (share) {
|
43
|
+
await share.tunnel.close();
|
44
|
+
this.activeShares.delete(shareId);
|
45
|
+
Logger.info(`🔒 Share ${shareId} closed`);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
getActiveShares() {
|
50
|
+
const shares = [];
|
51
|
+
for (const [id, share] of this.activeShares) {
|
52
|
+
const remainingTime = Math.max(0,
|
53
|
+
(share.startTime + share.duration - Date.now()) / 1000 / 60
|
54
|
+
);
|
55
|
+
|
56
|
+
shares.push({
|
57
|
+
id: share.id,
|
58
|
+
url: share.url,
|
59
|
+
remainingMinutes: Math.round(remainingTime)
|
60
|
+
});
|
61
|
+
}
|
62
|
+
return shares;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
module.exports = new ShareManager();
|
@@ -0,0 +1,38 @@
|
|
1
|
+
const os = require('os');
|
2
|
+
|
3
|
+
class ServerMonitor {
|
4
|
+
constructor() {
|
5
|
+
this.stats = {
|
6
|
+
startTime: Date.now(),
|
7
|
+
requests: 0,
|
8
|
+
errors: 0,
|
9
|
+
bytesTransferred: 0
|
10
|
+
};
|
11
|
+
}
|
12
|
+
|
13
|
+
recordRequest() {
|
14
|
+
this.stats.requests++;
|
15
|
+
}
|
16
|
+
|
17
|
+
recordError() {
|
18
|
+
this.stats.errors++;
|
19
|
+
}
|
20
|
+
|
21
|
+
recordTransfer(bytes) {
|
22
|
+
this.stats.bytesTransferred += bytes;
|
23
|
+
}
|
24
|
+
|
25
|
+
getStats() {
|
26
|
+
const uptime = (Date.now() - this.stats.startTime) / 1000;
|
27
|
+
return {
|
28
|
+
uptime: uptime.toFixed(2) + 's',
|
29
|
+
requests: this.stats.requests,
|
30
|
+
errors: this.stats.errors,
|
31
|
+
bytesTransferred: (this.stats.bytesTransferred / 1024 / 1024).toFixed(2) + 'MB',
|
32
|
+
memory: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + 'MB',
|
33
|
+
cpu: os.loadavg()[0].toFixed(2) + '%'
|
34
|
+
};
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
module.exports = new ServerMonitor();
|