shell-mirror 1.1.0 â 1.2.1
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/auto-start.js +239 -49
- package/package.json +1 -1
- package/public/index.html +2 -2
- package/server.js +0 -9
package/lib/auto-start.js
CHANGED
|
@@ -3,6 +3,8 @@ const path = require('path');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
const { spawn } = require('child_process');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const querystring = require('querystring');
|
|
6
8
|
|
|
7
9
|
class AutoStart {
|
|
8
10
|
constructor() {
|
|
@@ -11,11 +13,11 @@ class AutoStart {
|
|
|
11
13
|
this.configFile = path.join(this.configDir, 'config.json');
|
|
12
14
|
this.envFile = path.join(process.cwd(), '.env');
|
|
13
15
|
|
|
14
|
-
// Shell Mirror OAuth App credentials (
|
|
16
|
+
// Shell Mirror OAuth App credentials (production)
|
|
15
17
|
this.oauthConfig = {
|
|
16
|
-
clientId: '
|
|
17
|
-
clientSecret: '
|
|
18
|
-
redirectUri: 'http://localhost:
|
|
18
|
+
clientId: '804759223392-i5nv5csn1o6siqr760c99l2a9k4sammp.apps.googleusercontent.com',
|
|
19
|
+
clientSecret: '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',
|
|
20
|
+
redirectUri: 'http://localhost:8080/auth/google/callback'
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -66,13 +68,28 @@ class AutoStart {
|
|
|
66
68
|
|
|
67
69
|
async initiateLogin() {
|
|
68
70
|
console.log('');
|
|
69
|
-
console.log('
|
|
71
|
+
console.log('đ Opening browser for Google authentication...');
|
|
70
72
|
console.log('');
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
try {
|
|
75
|
+
// Start OAuth callback server
|
|
76
|
+
console.log('đ Starting OAuth callback server...');
|
|
77
|
+
const server = await this.createOAuthCallbackServer();
|
|
78
|
+
|
|
79
|
+
// Open browser for OAuth
|
|
80
|
+
const authUrl = this.buildAuthUrl();
|
|
81
|
+
console.log('đ Opening browser for login...');
|
|
82
|
+
await this.openBrowser(authUrl);
|
|
83
|
+
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log('đ¤ Please complete the Google login in your browser');
|
|
86
|
+
console.log(' (If browser didn\'t open, visit: ' + authUrl + ')');
|
|
87
|
+
console.log('');
|
|
88
|
+
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('â Failed to start OAuth flow:', error.message);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
76
93
|
}
|
|
77
94
|
|
|
78
95
|
buildAuthUrl() {
|
|
@@ -88,69 +105,114 @@ class AutoStart {
|
|
|
88
105
|
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
89
106
|
}
|
|
90
107
|
|
|
91
|
-
async
|
|
92
|
-
// This would create a temporary Express server to handle OAuth callback
|
|
93
|
-
// For now, we'll use a simplified approach
|
|
108
|
+
async createOAuthCallbackServer() {
|
|
94
109
|
const express = require('express');
|
|
95
110
|
const app = express();
|
|
96
111
|
|
|
97
112
|
return new Promise((resolve, reject) => {
|
|
113
|
+
let serverInstance;
|
|
114
|
+
|
|
98
115
|
app.get('/auth/google/callback', async (req, res) => {
|
|
99
116
|
try {
|
|
100
117
|
const code = req.query.code;
|
|
118
|
+
const error = req.query.error;
|
|
119
|
+
|
|
120
|
+
if (error) {
|
|
121
|
+
throw new Error(`OAuth error: ${error}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
101
124
|
if (code) {
|
|
125
|
+
console.log('đ Received authorization code, exchanging for tokens...');
|
|
126
|
+
|
|
102
127
|
// Exchange code for tokens
|
|
103
128
|
const tokens = await this.exchangeCodeForTokens(code);
|
|
104
129
|
await this.saveAuth(tokens);
|
|
105
130
|
|
|
106
131
|
res.send(`
|
|
107
132
|
<html>
|
|
108
|
-
<
|
|
109
|
-
<
|
|
110
|
-
<
|
|
111
|
-
|
|
133
|
+
<head>
|
|
134
|
+
<title>Shell Mirror - Login Successful</title>
|
|
135
|
+
<style>
|
|
136
|
+
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
|
|
137
|
+
.container { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 500px; margin: 0 auto; }
|
|
138
|
+
h2 { color: #4CAF50; margin-bottom: 20px; }
|
|
139
|
+
p { color: #666; margin-bottom: 15px; }
|
|
140
|
+
.email { color: #333; font-weight: bold; }
|
|
141
|
+
</style>
|
|
142
|
+
</head>
|
|
143
|
+
<body>
|
|
144
|
+
<div class="container">
|
|
145
|
+
<h2>â
Login Successful!</h2>
|
|
146
|
+
<p>Welcome, <span class="email">${tokens.email}</span></p>
|
|
147
|
+
<p>Shell Mirror is now starting with your account.</p>
|
|
148
|
+
<p>You can close this window and return to your terminal.</p>
|
|
149
|
+
<p><small>Or visit <strong>https://shellmirror.app</strong> on your phone to access your terminal.</small></p>
|
|
150
|
+
</div>
|
|
151
|
+
<script>
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
window.close();
|
|
154
|
+
}, 3000);
|
|
155
|
+
</script>
|
|
112
156
|
</body>
|
|
113
157
|
</html>
|
|
114
158
|
`);
|
|
115
159
|
|
|
116
|
-
//
|
|
160
|
+
// Close the temporary server and restart with authenticated state
|
|
117
161
|
setTimeout(() => {
|
|
118
|
-
console.log(
|
|
119
|
-
|
|
162
|
+
console.log(`â
Login successful! Welcome ${tokens.email}`);
|
|
163
|
+
serverInstance.close(() => {
|
|
164
|
+
this.startAuthenticatedServer(tokens);
|
|
165
|
+
});
|
|
120
166
|
}, 1000);
|
|
121
167
|
|
|
122
168
|
} else {
|
|
123
169
|
throw new Error('No authorization code received');
|
|
124
170
|
}
|
|
125
171
|
} catch (error) {
|
|
172
|
+
console.error('â OAuth callback error:', error.message);
|
|
126
173
|
res.send(`
|
|
127
174
|
<html>
|
|
128
|
-
<
|
|
129
|
-
<
|
|
130
|
-
<
|
|
131
|
-
|
|
175
|
+
<head>
|
|
176
|
+
<title>Shell Mirror - Login Failed</title>
|
|
177
|
+
<style>
|
|
178
|
+
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
|
|
179
|
+
.container { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 500px; margin: 0 auto; }
|
|
180
|
+
h2 { color: #f44336; margin-bottom: 20px; }
|
|
181
|
+
p { color: #666; margin-bottom: 15px; }
|
|
182
|
+
</style>
|
|
183
|
+
</head>
|
|
184
|
+
<body>
|
|
185
|
+
<div class="container">
|
|
186
|
+
<h2>â Login Failed</h2>
|
|
187
|
+
<p>Error: ${error.message}</p>
|
|
188
|
+
<p>Please try running 'shell-mirror' again.</p>
|
|
189
|
+
</div>
|
|
132
190
|
</body>
|
|
133
191
|
</html>
|
|
134
192
|
`);
|
|
135
193
|
}
|
|
136
194
|
});
|
|
137
195
|
|
|
138
|
-
|
|
139
|
-
|
|
196
|
+
serverInstance = app.listen(8080, (err) => {
|
|
197
|
+
if (err) {
|
|
198
|
+
reject(err);
|
|
199
|
+
} else {
|
|
200
|
+
resolve(serverInstance);
|
|
201
|
+
}
|
|
140
202
|
});
|
|
141
203
|
});
|
|
142
204
|
}
|
|
143
205
|
|
|
144
|
-
async
|
|
145
|
-
// Create
|
|
206
|
+
async createProductionConfig() {
|
|
207
|
+
// Create production environment file with real OAuth credentials
|
|
146
208
|
const envContent = `# Shell Mirror Configuration
|
|
147
209
|
# Auto-generated on ${new Date().toISOString()}
|
|
148
210
|
|
|
149
|
-
BASE_URL=http://localhost:
|
|
150
|
-
PORT=
|
|
211
|
+
BASE_URL=http://localhost:8080
|
|
212
|
+
PORT=8080
|
|
151
213
|
HOST=0.0.0.0
|
|
152
|
-
GOOGLE_CLIENT_ID
|
|
153
|
-
GOOGLE_CLIENT_SECRET
|
|
214
|
+
GOOGLE_CLIENT_ID=${this.oauthConfig.clientId}
|
|
215
|
+
GOOGLE_CLIENT_SECRET=${this.oauthConfig.clientSecret}
|
|
154
216
|
SESSION_SECRET=${crypto.randomBytes(32).toString('hex')}
|
|
155
217
|
NODE_ENV=development
|
|
156
218
|
`;
|
|
@@ -162,16 +224,21 @@ NODE_ENV=development
|
|
|
162
224
|
console.log('đ Starting Shell Mirror server...');
|
|
163
225
|
console.log('');
|
|
164
226
|
|
|
227
|
+
// Find the package root directory (where server.js is located)
|
|
228
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
229
|
+
const serverPath = path.join(packageRoot, 'server.js');
|
|
230
|
+
|
|
165
231
|
// Start the server in background
|
|
166
|
-
const serverProcess = spawn('node', [
|
|
232
|
+
const serverProcess = spawn('node', [serverPath], {
|
|
167
233
|
stdio: 'pipe',
|
|
168
|
-
cwd: path.dirname(this.envFile)
|
|
234
|
+
cwd: path.dirname(this.envFile),
|
|
235
|
+
env: { ...process.env }
|
|
169
236
|
});
|
|
170
237
|
|
|
171
238
|
// Wait a moment for server to start
|
|
172
239
|
setTimeout(() => {
|
|
173
240
|
this.displayLoginInstructions();
|
|
174
|
-
this.openBrowser('http://localhost:
|
|
241
|
+
this.openBrowser('http://localhost:8080');
|
|
175
242
|
}, 2000);
|
|
176
243
|
|
|
177
244
|
// Handle Ctrl+C gracefully
|
|
@@ -193,7 +260,7 @@ NODE_ENV=development
|
|
|
193
260
|
|
|
194
261
|
displayLoginInstructions() {
|
|
195
262
|
console.log('â
Shell Mirror server is running');
|
|
196
|
-
console.log('đ Local server: http://localhost:
|
|
263
|
+
console.log('đ Local server: http://localhost:8080');
|
|
197
264
|
console.log('đą Access from phone: https://shellmirror.app');
|
|
198
265
|
console.log('');
|
|
199
266
|
console.log('đ First-time setup:');
|
|
@@ -211,15 +278,95 @@ NODE_ENV=development
|
|
|
211
278
|
}
|
|
212
279
|
|
|
213
280
|
async exchangeCodeForTokens(code) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
const postData = querystring.stringify({
|
|
283
|
+
code: code,
|
|
284
|
+
client_id: this.oauthConfig.clientId,
|
|
285
|
+
client_secret: this.oauthConfig.clientSecret,
|
|
286
|
+
redirect_uri: this.oauthConfig.redirectUri,
|
|
287
|
+
grant_type: 'authorization_code'
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const options = {
|
|
291
|
+
hostname: 'oauth2.googleapis.com',
|
|
292
|
+
port: 443,
|
|
293
|
+
path: '/token',
|
|
294
|
+
method: 'POST',
|
|
295
|
+
headers: {
|
|
296
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
297
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const req = https.request(options, (res) => {
|
|
302
|
+
let data = '';
|
|
303
|
+
res.on('data', (chunk) => {
|
|
304
|
+
data += chunk;
|
|
305
|
+
});
|
|
306
|
+
res.on('end', async () => {
|
|
307
|
+
try {
|
|
308
|
+
const tokens = JSON.parse(data);
|
|
309
|
+
if (tokens.access_token) {
|
|
310
|
+
// Get user info
|
|
311
|
+
const userInfo = await this.getUserInfo(tokens.access_token);
|
|
312
|
+
resolve({
|
|
313
|
+
accessToken: tokens.access_token,
|
|
314
|
+
refreshToken: tokens.refresh_token,
|
|
315
|
+
email: userInfo.email,
|
|
316
|
+
name: userInfo.name,
|
|
317
|
+
expiresAt: Date.now() + (tokens.expires_in * 1000)
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
reject(new Error('Failed to get access token: ' + data));
|
|
321
|
+
}
|
|
322
|
+
} catch (error) {
|
|
323
|
+
reject(error);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
req.on('error', (error) => {
|
|
329
|
+
reject(error);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
req.write(postData);
|
|
333
|
+
req.end();
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async getUserInfo(accessToken) {
|
|
338
|
+
return new Promise((resolve, reject) => {
|
|
339
|
+
const options = {
|
|
340
|
+
hostname: 'www.googleapis.com',
|
|
341
|
+
port: 443,
|
|
342
|
+
path: '/oauth2/v2/userinfo',
|
|
343
|
+
method: 'GET',
|
|
344
|
+
headers: {
|
|
345
|
+
'Authorization': `Bearer ${accessToken}`
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const req = https.request(options, (res) => {
|
|
350
|
+
let data = '';
|
|
351
|
+
res.on('data', (chunk) => {
|
|
352
|
+
data += chunk;
|
|
353
|
+
});
|
|
354
|
+
res.on('end', () => {
|
|
355
|
+
try {
|
|
356
|
+
const userInfo = JSON.parse(data);
|
|
357
|
+
resolve(userInfo);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
reject(error);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
req.on('error', (error) => {
|
|
365
|
+
reject(error);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
req.end();
|
|
369
|
+
});
|
|
223
370
|
}
|
|
224
371
|
|
|
225
372
|
async saveAuth(authData) {
|
|
@@ -235,10 +382,15 @@ NODE_ENV=development
|
|
|
235
382
|
console.log('đ Starting server...');
|
|
236
383
|
console.log('');
|
|
237
384
|
|
|
385
|
+
// Find the package root directory (where server.js is located)
|
|
386
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
387
|
+
const serverPath = path.join(packageRoot, 'server.js');
|
|
388
|
+
|
|
238
389
|
// Start the main server process
|
|
239
|
-
const serverProcess = spawn('node', [
|
|
390
|
+
const serverProcess = spawn('node', [serverPath], {
|
|
240
391
|
stdio: 'inherit',
|
|
241
|
-
cwd: path.dirname(this.envFile)
|
|
392
|
+
cwd: path.dirname(this.envFile),
|
|
393
|
+
env: { ...process.env }
|
|
242
394
|
});
|
|
243
395
|
|
|
244
396
|
// Display status information
|
|
@@ -265,8 +417,8 @@ NODE_ENV=development
|
|
|
265
417
|
const envContent = `# Shell Mirror Configuration
|
|
266
418
|
# Auto-generated on ${new Date().toISOString()}
|
|
267
419
|
|
|
268
|
-
BASE_URL=http://localhost:
|
|
269
|
-
PORT=
|
|
420
|
+
BASE_URL=http://localhost:8080
|
|
421
|
+
PORT=8080
|
|
270
422
|
HOST=0.0.0.0
|
|
271
423
|
GOOGLE_CLIENT_ID=${this.oauthConfig.clientId}
|
|
272
424
|
GOOGLE_CLIENT_SECRET=${this.oauthConfig.clientSecret}
|
|
@@ -285,7 +437,7 @@ ACCESS_TOKEN=${authInfo.accessToken}
|
|
|
285
437
|
displayStatus(authInfo) {
|
|
286
438
|
console.log('â
Shell Mirror is running');
|
|
287
439
|
console.log(`đ¤ Logged in as: ${authInfo.email}`);
|
|
288
|
-
console.log('đ Local server: http://localhost:
|
|
440
|
+
console.log('đ Local server: http://localhost:8080');
|
|
289
441
|
console.log('đą Access from phone: https://shellmirror.app');
|
|
290
442
|
console.log('');
|
|
291
443
|
console.log('âšī¸ To use from your phone:');
|
|
@@ -297,6 +449,44 @@ ACCESS_TOKEN=${authInfo.accessToken}
|
|
|
297
449
|
console.log('');
|
|
298
450
|
}
|
|
299
451
|
|
|
452
|
+
async startAuthenticatedServer(authInfo) {
|
|
453
|
+
console.log('');
|
|
454
|
+
console.log('đ Starting authenticated Shell Mirror server...');
|
|
455
|
+
|
|
456
|
+
// Create .env file with authentication
|
|
457
|
+
await this.createEnvFile(authInfo);
|
|
458
|
+
|
|
459
|
+
// Find the package root directory (where server.js is located)
|
|
460
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
461
|
+
const serverPath = path.join(packageRoot, 'server.js');
|
|
462
|
+
|
|
463
|
+
// Start the main server process
|
|
464
|
+
const serverProcess = spawn('node', [serverPath], {
|
|
465
|
+
stdio: 'inherit',
|
|
466
|
+
cwd: path.dirname(this.envFile),
|
|
467
|
+
env: { ...process.env }
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Display status information
|
|
471
|
+
this.displayStatus(authInfo);
|
|
472
|
+
|
|
473
|
+
// Handle Ctrl+C gracefully
|
|
474
|
+
process.on('SIGINT', () => {
|
|
475
|
+
console.log('');
|
|
476
|
+
console.log('đ Stopping Shell Mirror...');
|
|
477
|
+
serverProcess.kill('SIGINT');
|
|
478
|
+
process.exit(0);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Wait for server process
|
|
482
|
+
serverProcess.on('close', (code) => {
|
|
483
|
+
if (code !== 0) {
|
|
484
|
+
console.error(`â Server exited with code ${code}`);
|
|
485
|
+
process.exit(code);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
300
490
|
async openBrowser(url) {
|
|
301
491
|
const platform = os.platform();
|
|
302
492
|
let command;
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -729,7 +729,7 @@
|
|
|
729
729
|
// Check if local server is running
|
|
730
730
|
async function checkLocalServer() {
|
|
731
731
|
try {
|
|
732
|
-
const response = await fetch('http://localhost:
|
|
732
|
+
const response = await fetch('http://localhost:8080/api/auth/status', {
|
|
733
733
|
method: 'GET',
|
|
734
734
|
timeout: 2000
|
|
735
735
|
});
|
|
@@ -749,7 +749,7 @@
|
|
|
749
749
|
}
|
|
750
750
|
|
|
751
751
|
// Redirect to Google OAuth
|
|
752
|
-
window.location.href = 'http://localhost:
|
|
752
|
+
window.location.href = 'http://localhost:8080/auth/google';
|
|
753
753
|
}
|
|
754
754
|
|
|
755
755
|
// Show installation reminder modal
|
package/server.js
CHANGED
|
@@ -100,15 +100,6 @@ if (missingEnvVars.length > 0) {
|
|
|
100
100
|
process.exit(1);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
// Check if OAuth credentials are placeholders (first-time setup)
|
|
104
|
-
const isFirstTimeSetup = process.env.GOOGLE_CLIENT_ID === 'placeholder-will-be-set-after-login';
|
|
105
|
-
|
|
106
|
-
if (isFirstTimeSetup) {
|
|
107
|
-
console.log('âšī¸ First-time setup detected. OAuth authentication is not yet configured.');
|
|
108
|
-
console.log(' The server will start, but Google login will not work until configured.');
|
|
109
|
-
console.log(' Please visit the web interface to complete setup.');
|
|
110
|
-
console.log('');
|
|
111
|
-
}
|
|
112
103
|
|
|
113
104
|
// 4. WebSocket Connection Handler with Authentication
|
|
114
105
|
wss.on('connection', function connection(ws, request) {
|