nstantpage-agent 0.5.32 → 0.5.33
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/dist/localServer.js +131 -3
- package/dist/tunnel.js +4 -0
- package/package.json +1 -1
package/dist/localServer.js
CHANGED
|
@@ -180,10 +180,10 @@ export class LocalServer {
|
|
|
180
180
|
}
|
|
181
181
|
async start() {
|
|
182
182
|
this.server = http.createServer(async (req, res) => {
|
|
183
|
-
// CORS
|
|
183
|
+
// CORS — match gateway's headers so direct-localhost works from nstantpage.com
|
|
184
184
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
185
185
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
186
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
186
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, X-Project-Id');
|
|
187
187
|
if (req.method === 'OPTIONS') {
|
|
188
188
|
res.statusCode = 204;
|
|
189
189
|
res.end();
|
|
@@ -201,6 +201,127 @@ export class LocalServer {
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
});
|
|
204
|
+
// Handle WebSocket upgrades for direct-localhost terminal connections
|
|
205
|
+
// (same path as gateway: /live/terminal/ws/{projectId}/{sessionId})
|
|
206
|
+
this.server.on('upgrade', (req, socket, head) => {
|
|
207
|
+
const url = req.url || '';
|
|
208
|
+
const match = url.match(/^\/live\/terminal\/ws\/([^/]+)\/([^/?]+)/);
|
|
209
|
+
if (!match) {
|
|
210
|
+
socket.destroy();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const sessionId = match[2];
|
|
214
|
+
const session = getTerminalSession(sessionId);
|
|
215
|
+
if (!session) {
|
|
216
|
+
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
|
|
217
|
+
socket.destroy();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Complete WebSocket handshake (RFC 6455)
|
|
221
|
+
const key = req.headers['sec-websocket-key'];
|
|
222
|
+
if (!key) {
|
|
223
|
+
socket.destroy();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const crypto = require('crypto');
|
|
227
|
+
const accept = crypto.createHash('sha1')
|
|
228
|
+
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
229
|
+
.digest('base64');
|
|
230
|
+
socket.write('HTTP/1.1 101 Switching Protocols\r\n' +
|
|
231
|
+
'Upgrade: websocket\r\n' +
|
|
232
|
+
'Connection: Upgrade\r\n' +
|
|
233
|
+
`Sec-WebSocket-Accept: ${accept}\r\n` +
|
|
234
|
+
'Access-Control-Allow-Origin: *\r\n' +
|
|
235
|
+
'\r\n');
|
|
236
|
+
// Wire PTY ↔ WebSocket (same JSON protocol as tunnel relay)
|
|
237
|
+
const sendFrame = (data) => {
|
|
238
|
+
const buf = Buffer.from(data, 'utf-8');
|
|
239
|
+
const frame = Buffer.alloc(buf.length < 126 ? 2 + buf.length : 4 + buf.length);
|
|
240
|
+
frame[0] = 0x81; // text frame, FIN
|
|
241
|
+
if (buf.length < 126) {
|
|
242
|
+
frame[1] = buf.length;
|
|
243
|
+
buf.copy(frame, 2);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
frame[1] = 126;
|
|
247
|
+
frame.writeUInt16BE(buf.length, 2);
|
|
248
|
+
buf.copy(frame, 4);
|
|
249
|
+
}
|
|
250
|
+
if (!socket.destroyed)
|
|
251
|
+
socket.write(frame);
|
|
252
|
+
};
|
|
253
|
+
// Send connection confirmation
|
|
254
|
+
sendFrame(JSON.stringify({ type: 'connected', sessionId, projectId: match[1] }));
|
|
255
|
+
const cleanup = attachTerminalClient(sessionId, {
|
|
256
|
+
onData: (data) => sendFrame(JSON.stringify({ type: 'output', data })),
|
|
257
|
+
onExit: (code) => {
|
|
258
|
+
sendFrame(JSON.stringify({ type: 'exit', exitCode: code }));
|
|
259
|
+
socket.end();
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
// Parse incoming WebSocket frames from browser
|
|
263
|
+
let pending = Buffer.alloc(0);
|
|
264
|
+
socket.on('data', (chunk) => {
|
|
265
|
+
pending = Buffer.concat([pending, chunk]);
|
|
266
|
+
while (pending.length >= 2) {
|
|
267
|
+
const masked = (pending[1] & 0x80) !== 0;
|
|
268
|
+
let payloadLen = pending[1] & 0x7f;
|
|
269
|
+
let offset = 2;
|
|
270
|
+
if (payloadLen === 126) {
|
|
271
|
+
if (pending.length < 4)
|
|
272
|
+
return;
|
|
273
|
+
payloadLen = pending.readUInt16BE(2);
|
|
274
|
+
offset = 4;
|
|
275
|
+
}
|
|
276
|
+
else if (payloadLen === 127) {
|
|
277
|
+
if (pending.length < 10)
|
|
278
|
+
return;
|
|
279
|
+
payloadLen = Number(pending.readBigUInt64BE(2));
|
|
280
|
+
offset = 10;
|
|
281
|
+
}
|
|
282
|
+
if (masked)
|
|
283
|
+
offset += 4;
|
|
284
|
+
if (pending.length < offset + payloadLen)
|
|
285
|
+
return;
|
|
286
|
+
const opcode = pending[0] & 0x0f;
|
|
287
|
+
if (opcode === 0x08) { // close
|
|
288
|
+
socket.end();
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
let payload = pending.subarray(offset, offset + payloadLen);
|
|
292
|
+
if (masked) {
|
|
293
|
+
const maskKey = pending.subarray(offset - 4, offset);
|
|
294
|
+
payload = Buffer.from(payload);
|
|
295
|
+
for (let i = 0; i < payload.length; i++) {
|
|
296
|
+
payload[i] ^= maskKey[i % 4];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (opcode === 0x01) { // text — JSON messages from frontend
|
|
300
|
+
try {
|
|
301
|
+
const msg = JSON.parse(payload.toString('utf-8'));
|
|
302
|
+
if (msg.type === 'input' && typeof msg.data === 'string') {
|
|
303
|
+
writeToTerminalSession(session, msg.data);
|
|
304
|
+
session.lastActivity = Date.now();
|
|
305
|
+
}
|
|
306
|
+
else if (msg.type === 'resize' && typeof msg.cols === 'number' && typeof msg.rows === 'number') {
|
|
307
|
+
resizeTerminalSession(session, msg.cols, msg.rows);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch { /* invalid JSON — ignore */ }
|
|
311
|
+
}
|
|
312
|
+
else if (opcode === 0x09) { // ping
|
|
313
|
+
const pong = Buffer.alloc(2 + payloadLen);
|
|
314
|
+
pong[0] = 0x8a;
|
|
315
|
+
pong[1] = payloadLen;
|
|
316
|
+
payload.copy(pong, 2);
|
|
317
|
+
socket.write(pong);
|
|
318
|
+
}
|
|
319
|
+
pending = pending.subarray(offset + payloadLen);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
socket.on('close', () => cleanup?.());
|
|
323
|
+
socket.on('error', () => cleanup?.());
|
|
324
|
+
});
|
|
204
325
|
await new Promise((resolve, reject) => {
|
|
205
326
|
this.server.listen(this.options.apiPort, '127.0.0.1', () => {
|
|
206
327
|
console.log(` [LocalServer] API server on port ${this.options.apiPort}`);
|
|
@@ -519,7 +640,14 @@ export class LocalServer {
|
|
|
519
640
|
const rows = parsed.rows || 30;
|
|
520
641
|
// Determine shell
|
|
521
642
|
const shellCmd = process.platform === 'win32' ? 'cmd.exe' : (process.env.SHELL || '/bin/bash');
|
|
522
|
-
const shellEnv = {
|
|
643
|
+
const shellEnv = {
|
|
644
|
+
...process.env,
|
|
645
|
+
TERM: 'xterm-256color',
|
|
646
|
+
COLORTERM: 'truecolor',
|
|
647
|
+
FORCE_COLOR: '3',
|
|
648
|
+
COLUMNS: String(cols),
|
|
649
|
+
LINES: String(rows),
|
|
650
|
+
};
|
|
523
651
|
sessionCounter++;
|
|
524
652
|
const label = parsed.label || `Terminal ${sessionCounter}`;
|
|
525
653
|
let shell = null;
|
package/dist/tunnel.js
CHANGED
|
@@ -475,6 +475,10 @@ export class TunnelClient {
|
|
|
475
475
|
case 'start-project':
|
|
476
476
|
this.handleStartProject(msg.projectId, msg.clean);
|
|
477
477
|
break;
|
|
478
|
+
case 'force-disconnect':
|
|
479
|
+
console.log(' [Tunnel] Received force-disconnect — will not reconnect');
|
|
480
|
+
this.shouldReconnect = false;
|
|
481
|
+
break;
|
|
478
482
|
default:
|
|
479
483
|
console.warn(` [Tunnel] Unknown message type: ${msg.type}`);
|
|
480
484
|
}
|
package/package.json
CHANGED