shell-mirror 1.1.0 â 1.2.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/lib/auto-start.js +212 -37
- package/package.json +1 -1
- 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,10 +13,10 @@ 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
|
+
clientId: '804759223392-i5nv5csn1o6siqr760c99l2a9k4sammp.apps.googleusercontent.com',
|
|
19
|
+
clientSecret: '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',
|
|
18
20
|
redirectUri: 'http://localhost:3000/auth/google/callback'
|
|
19
21
|
};
|
|
20
22
|
}
|
|
@@ -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(3000, (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
211
|
BASE_URL=http://localhost:3000
|
|
150
212
|
PORT=3000
|
|
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
|
`;
|
|
@@ -211,15 +273,95 @@ NODE_ENV=development
|
|
|
211
273
|
}
|
|
212
274
|
|
|
213
275
|
async exchangeCodeForTokens(code) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
276
|
+
return new Promise((resolve, reject) => {
|
|
277
|
+
const postData = querystring.stringify({
|
|
278
|
+
code: code,
|
|
279
|
+
client_id: this.oauthConfig.clientId,
|
|
280
|
+
client_secret: this.oauthConfig.clientSecret,
|
|
281
|
+
redirect_uri: this.oauthConfig.redirectUri,
|
|
282
|
+
grant_type: 'authorization_code'
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const options = {
|
|
286
|
+
hostname: 'oauth2.googleapis.com',
|
|
287
|
+
port: 443,
|
|
288
|
+
path: '/token',
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers: {
|
|
291
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
292
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const req = https.request(options, (res) => {
|
|
297
|
+
let data = '';
|
|
298
|
+
res.on('data', (chunk) => {
|
|
299
|
+
data += chunk;
|
|
300
|
+
});
|
|
301
|
+
res.on('end', async () => {
|
|
302
|
+
try {
|
|
303
|
+
const tokens = JSON.parse(data);
|
|
304
|
+
if (tokens.access_token) {
|
|
305
|
+
// Get user info
|
|
306
|
+
const userInfo = await this.getUserInfo(tokens.access_token);
|
|
307
|
+
resolve({
|
|
308
|
+
accessToken: tokens.access_token,
|
|
309
|
+
refreshToken: tokens.refresh_token,
|
|
310
|
+
email: userInfo.email,
|
|
311
|
+
name: userInfo.name,
|
|
312
|
+
expiresAt: Date.now() + (tokens.expires_in * 1000)
|
|
313
|
+
});
|
|
314
|
+
} else {
|
|
315
|
+
reject(new Error('Failed to get access token: ' + data));
|
|
316
|
+
}
|
|
317
|
+
} catch (error) {
|
|
318
|
+
reject(error);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
req.on('error', (error) => {
|
|
324
|
+
reject(error);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
req.write(postData);
|
|
328
|
+
req.end();
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async getUserInfo(accessToken) {
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
334
|
+
const options = {
|
|
335
|
+
hostname: 'www.googleapis.com',
|
|
336
|
+
port: 443,
|
|
337
|
+
path: '/oauth2/v2/userinfo',
|
|
338
|
+
method: 'GET',
|
|
339
|
+
headers: {
|
|
340
|
+
'Authorization': `Bearer ${accessToken}`
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const req = https.request(options, (res) => {
|
|
345
|
+
let data = '';
|
|
346
|
+
res.on('data', (chunk) => {
|
|
347
|
+
data += chunk;
|
|
348
|
+
});
|
|
349
|
+
res.on('end', () => {
|
|
350
|
+
try {
|
|
351
|
+
const userInfo = JSON.parse(data);
|
|
352
|
+
resolve(userInfo);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
reject(error);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
req.on('error', (error) => {
|
|
360
|
+
reject(error);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
req.end();
|
|
364
|
+
});
|
|
223
365
|
}
|
|
224
366
|
|
|
225
367
|
async saveAuth(authData) {
|
|
@@ -297,6 +439,39 @@ ACCESS_TOKEN=${authInfo.accessToken}
|
|
|
297
439
|
console.log('');
|
|
298
440
|
}
|
|
299
441
|
|
|
442
|
+
async startAuthenticatedServer(authInfo) {
|
|
443
|
+
console.log('');
|
|
444
|
+
console.log('đ Starting authenticated Shell Mirror server...');
|
|
445
|
+
|
|
446
|
+
// Create .env file with authentication
|
|
447
|
+
await this.createEnvFile(authInfo);
|
|
448
|
+
|
|
449
|
+
// Start the main server process
|
|
450
|
+
const serverProcess = spawn('node', ['server.js'], {
|
|
451
|
+
stdio: 'inherit',
|
|
452
|
+
cwd: path.dirname(this.envFile)
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Display status information
|
|
456
|
+
this.displayStatus(authInfo);
|
|
457
|
+
|
|
458
|
+
// Handle Ctrl+C gracefully
|
|
459
|
+
process.on('SIGINT', () => {
|
|
460
|
+
console.log('');
|
|
461
|
+
console.log('đ Stopping Shell Mirror...');
|
|
462
|
+
serverProcess.kill('SIGINT');
|
|
463
|
+
process.exit(0);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Wait for server process
|
|
467
|
+
serverProcess.on('close', (code) => {
|
|
468
|
+
if (code !== 0) {
|
|
469
|
+
console.error(`â Server exited with code ${code}`);
|
|
470
|
+
process.exit(code);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
300
475
|
async openBrowser(url) {
|
|
301
476
|
const platform = os.platform();
|
|
302
477
|
let command;
|
package/package.json
CHANGED
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) {
|