sessioncast-cli 2.3.1 → 2.4.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/dist/agent/runner.js +27 -1
- package/dist/commands/login.js +72 -10
- package/dist/config.d.ts +3 -0
- package/dist/config.js +14 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/agent/tunnel-handler.d.ts +0 -12
- package/dist/agent/tunnel-handler.js +0 -180
package/dist/agent/runner.js
CHANGED
|
@@ -120,7 +120,33 @@ class AgentRunner {
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
if (!finalPath || !fs.existsSync(finalPath)) {
|
|
123
|
-
|
|
123
|
+
const isLoggedIn = (0, config_1.getAccessToken)() || (0, config_1.getAgentToken)();
|
|
124
|
+
if (isLoggedIn) {
|
|
125
|
+
// User is logged in but agent token not yet generated
|
|
126
|
+
throw new Error('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
|
|
127
|
+
' Agent token not yet generated\n' +
|
|
128
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' +
|
|
129
|
+
' Please run `sessioncast login` again to generate an agent token.\n\n' +
|
|
130
|
+
' Or create a config file manually:\n' +
|
|
131
|
+
' $ cat > ~/.sessioncast.yml << \'EOF\'\n' +
|
|
132
|
+
' machineId: my-machine\n' +
|
|
133
|
+
' relay: wss://relay.sessioncast.io/ws\n' +
|
|
134
|
+
' token: agt_YOUR_TOKEN\n' +
|
|
135
|
+
' EOF\n\n' +
|
|
136
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
137
|
+
}
|
|
138
|
+
throw new Error('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
|
|
139
|
+
' Not logged in\n' +
|
|
140
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' +
|
|
141
|
+
' Please login first:\n' +
|
|
142
|
+
' $ sessioncast login\n\n' +
|
|
143
|
+
' Or create a config file manually:\n' +
|
|
144
|
+
' $ cat > ~/.sessioncast.yml << \'EOF\'\n' +
|
|
145
|
+
' machineId: my-machine\n' +
|
|
146
|
+
' relay: wss://relay.sessioncast.io/ws\n' +
|
|
147
|
+
' token: agt_YOUR_TOKEN\n' +
|
|
148
|
+
' EOF\n\n' +
|
|
149
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
124
150
|
}
|
|
125
151
|
console.log(`Loading config from: ${finalPath}`);
|
|
126
152
|
const content = fs.readFileSync(finalPath, 'utf-8');
|
package/dist/commands/login.js
CHANGED
|
@@ -214,19 +214,81 @@ async function logout() {
|
|
|
214
214
|
(0, config_1.clearAuth)();
|
|
215
215
|
console.log(chalk_1.default.green('\u2713 Logged out successfully!'));
|
|
216
216
|
}
|
|
217
|
+
function formatExpiry(expiresAt) {
|
|
218
|
+
const now = Date.now();
|
|
219
|
+
if (now > expiresAt) {
|
|
220
|
+
return chalk_1.default.red('expired');
|
|
221
|
+
}
|
|
222
|
+
const remaining = expiresAt - now;
|
|
223
|
+
const hours = Math.floor(remaining / (1000 * 60 * 60));
|
|
224
|
+
const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
|
|
225
|
+
if (hours > 24) {
|
|
226
|
+
const days = Math.floor(hours / 24);
|
|
227
|
+
return chalk_1.default.green(`${days}d ${hours % 24}h remaining`);
|
|
228
|
+
}
|
|
229
|
+
if (hours > 0) {
|
|
230
|
+
return chalk_1.default.green(`${hours}h ${minutes}m remaining`);
|
|
231
|
+
}
|
|
232
|
+
return chalk_1.default.yellow(`${minutes}m remaining`);
|
|
233
|
+
}
|
|
217
234
|
function status() {
|
|
218
235
|
const accessToken = (0, config_1.getAccessToken)();
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
236
|
+
const rawAccessToken = (0, config_1.getRawAccessToken)();
|
|
237
|
+
const agentToken = (0, config_1.getAgentToken)();
|
|
238
|
+
const apiKey = (0, config_1.getApiKey)();
|
|
239
|
+
const refreshToken = (0, config_1.getRefreshToken)();
|
|
240
|
+
const machineId = (0, config_1.getMachineId)();
|
|
241
|
+
const expiresAt = (0, config_1.getTokenExpiresAt)();
|
|
242
|
+
console.log('');
|
|
243
|
+
if (!(0, config_1.isLoggedIn)()) {
|
|
244
|
+
console.log(chalk_1.default.yellow(' Not logged in'));
|
|
245
|
+
console.log(chalk_1.default.gray(' Run: sessioncast login\n'));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
console.log(chalk_1.default.green.bold(' \u2713 Logged in\n'));
|
|
249
|
+
// Auth method
|
|
250
|
+
if (accessToken) {
|
|
251
|
+
console.log(chalk_1.default.gray(' Auth method: ') + 'OAuth');
|
|
252
|
+
}
|
|
253
|
+
else if (rawAccessToken) {
|
|
254
|
+
console.log(chalk_1.default.gray(' Auth method: ') + chalk_1.default.yellow('OAuth (token expired)'));
|
|
255
|
+
}
|
|
256
|
+
else if (apiKey) {
|
|
257
|
+
console.log(chalk_1.default.gray(' Auth method: ') + 'API Key');
|
|
227
258
|
}
|
|
228
259
|
else {
|
|
229
|
-
console.log(chalk_1.default.
|
|
230
|
-
|
|
260
|
+
console.log(chalk_1.default.gray(' Auth method: ') + 'Agent Token');
|
|
261
|
+
}
|
|
262
|
+
if (machineId) {
|
|
263
|
+
console.log(chalk_1.default.gray(' Machine ID: ') + machineId);
|
|
264
|
+
}
|
|
265
|
+
console.log('');
|
|
266
|
+
// Tokens
|
|
267
|
+
console.log(chalk_1.default.bold(' Tokens'));
|
|
268
|
+
if (agentToken) {
|
|
269
|
+
console.log(chalk_1.default.gray(' Agent Token: ') + agentToken);
|
|
270
|
+
}
|
|
271
|
+
if (rawAccessToken) {
|
|
272
|
+
const expiryStr = expiresAt ? ` (${formatExpiry(expiresAt)})` : '';
|
|
273
|
+
console.log(chalk_1.default.gray(' Access Token: ') + rawAccessToken + expiryStr);
|
|
274
|
+
}
|
|
275
|
+
if (refreshToken) {
|
|
276
|
+
console.log(chalk_1.default.gray(' Refresh Token: ') + refreshToken);
|
|
277
|
+
}
|
|
278
|
+
if (apiKey) {
|
|
279
|
+
console.log(chalk_1.default.gray(' API Key: ') + apiKey);
|
|
280
|
+
}
|
|
281
|
+
if (!agentToken && !rawAccessToken && !refreshToken && !apiKey) {
|
|
282
|
+
console.log(chalk_1.default.gray(' (none)'));
|
|
231
283
|
}
|
|
284
|
+
console.log('');
|
|
285
|
+
// Endpoints
|
|
286
|
+
console.log(chalk_1.default.bold(' Endpoints'));
|
|
287
|
+
console.log(chalk_1.default.gray(' API: ') + (0, config_1.getApiUrl)());
|
|
288
|
+
console.log(chalk_1.default.gray(' Auth: ') + (0, config_1.getAuthUrl)());
|
|
289
|
+
console.log(chalk_1.default.gray(' Relay: ') + (0, config_1.getRelayUrl)());
|
|
290
|
+
console.log('');
|
|
291
|
+
// Config path
|
|
292
|
+
console.log(chalk_1.default.gray(` Config: ${(0, config_1.getConfigPath)()}`));
|
|
293
|
+
console.log('');
|
|
232
294
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -33,4 +33,7 @@ export declare function clearAuth(): void;
|
|
|
33
33
|
export declare function isLoggedIn(): boolean;
|
|
34
34
|
export declare function hasSeenWelcome(): boolean;
|
|
35
35
|
export declare function setSeenWelcome(): void;
|
|
36
|
+
export declare function getConfigPath(): string;
|
|
37
|
+
export declare function getRawAccessToken(): string | undefined;
|
|
38
|
+
export declare function getTokenExpiresAt(): number | undefined;
|
|
36
39
|
export default config;
|
package/dist/config.js
CHANGED
|
@@ -24,6 +24,9 @@ exports.clearAuth = clearAuth;
|
|
|
24
24
|
exports.isLoggedIn = isLoggedIn;
|
|
25
25
|
exports.hasSeenWelcome = hasSeenWelcome;
|
|
26
26
|
exports.setSeenWelcome = setSeenWelcome;
|
|
27
|
+
exports.getConfigPath = getConfigPath;
|
|
28
|
+
exports.getRawAccessToken = getRawAccessToken;
|
|
29
|
+
exports.getTokenExpiresAt = getTokenExpiresAt;
|
|
27
30
|
const conf_1 = __importDefault(require("conf"));
|
|
28
31
|
const config = new conf_1.default({
|
|
29
32
|
projectName: 'sessioncast',
|
|
@@ -110,4 +113,15 @@ function hasSeenWelcome() {
|
|
|
110
113
|
function setSeenWelcome() {
|
|
111
114
|
config.set('hasSeenWelcome', true);
|
|
112
115
|
}
|
|
116
|
+
// Config file path
|
|
117
|
+
function getConfigPath() {
|
|
118
|
+
return config.path;
|
|
119
|
+
}
|
|
120
|
+
// Raw token access (bypass expiry check, for status display)
|
|
121
|
+
function getRawAccessToken() {
|
|
122
|
+
return config.get('accessToken');
|
|
123
|
+
}
|
|
124
|
+
function getTokenExpiresAt() {
|
|
125
|
+
return config.get('tokenExpiresAt');
|
|
126
|
+
}
|
|
113
127
|
exports.default = config;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export declare class TunnelHandler {
|
|
2
|
-
private port;
|
|
3
|
-
private host;
|
|
4
|
-
private activeRequests;
|
|
5
|
-
private readonly MAX_CONCURRENT;
|
|
6
|
-
private readonly MAX_RESPONSE_SIZE;
|
|
7
|
-
private readonly CHUNK_SIZE;
|
|
8
|
-
constructor(port: number, host?: string);
|
|
9
|
-
handleRequest(requestId: string, method: string, path: string, queryString: string, headers: Record<string, string>, body: string | undefined, sendResponse: (msg: any) => void): Promise<void>;
|
|
10
|
-
private getBase64ChunkSize;
|
|
11
|
-
private makeHttpRequest;
|
|
12
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.TunnelHandler = void 0;
|
|
37
|
-
const http = __importStar(require("http"));
|
|
38
|
-
const debug_1 = require("./debug");
|
|
39
|
-
const ALLOWED_HOSTS = ['localhost', '127.0.0.1', '::1'];
|
|
40
|
-
class TunnelHandler {
|
|
41
|
-
constructor(port, host = 'localhost') {
|
|
42
|
-
this.activeRequests = 0;
|
|
43
|
-
this.MAX_CONCURRENT = 10;
|
|
44
|
-
this.MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
45
|
-
this.CHUNK_SIZE = 200 * 1024; // 200KB before base64
|
|
46
|
-
this.port = port;
|
|
47
|
-
this.host = host;
|
|
48
|
-
}
|
|
49
|
-
async handleRequest(requestId, method, path, queryString, headers, body, sendResponse) {
|
|
50
|
-
// Check concurrent limit
|
|
51
|
-
if (this.activeRequests >= this.MAX_CONCURRENT) {
|
|
52
|
-
sendResponse({
|
|
53
|
-
requestId,
|
|
54
|
-
statusCode: 503,
|
|
55
|
-
headers: { 'content-type': 'text/plain' },
|
|
56
|
-
body: Buffer.from('Too many concurrent requests').toString('base64'),
|
|
57
|
-
});
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
// Validate host (SSRF prevention)
|
|
61
|
-
if (!ALLOWED_HOSTS.includes(this.host)) {
|
|
62
|
-
sendResponse({
|
|
63
|
-
requestId,
|
|
64
|
-
statusCode: 403,
|
|
65
|
-
headers: { 'content-type': 'text/plain' },
|
|
66
|
-
body: Buffer.from('Forbidden: only localhost connections allowed').toString('base64'),
|
|
67
|
-
});
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
this.activeRequests++;
|
|
71
|
-
try {
|
|
72
|
-
const fullPath = queryString ? `${path}?${queryString}` : path;
|
|
73
|
-
const requestBody = body ? Buffer.from(body, 'base64') : undefined;
|
|
74
|
-
(0, debug_1.debugLog)('TUNNEL', `${method} ${fullPath} -> ${this.host}:${this.port}`);
|
|
75
|
-
const { statusCode, responseHeaders, responseBody } = await this.makeHttpRequest(method, fullPath, headers, requestBody);
|
|
76
|
-
const bodyBase64 = responseBody.toString('base64');
|
|
77
|
-
if (responseBody.length > this.CHUNK_SIZE) {
|
|
78
|
-
// Split into chunks
|
|
79
|
-
const totalChunks = Math.ceil(bodyBase64.length / this.getBase64ChunkSize());
|
|
80
|
-
for (let i = 0; i < totalChunks; i++) {
|
|
81
|
-
const start = i * this.getBase64ChunkSize();
|
|
82
|
-
const end = Math.min(start + this.getBase64ChunkSize(), bodyBase64.length);
|
|
83
|
-
const chunk = bodyBase64.slice(start, end);
|
|
84
|
-
const isFinal = i === totalChunks - 1;
|
|
85
|
-
sendResponse({
|
|
86
|
-
requestId,
|
|
87
|
-
statusCode,
|
|
88
|
-
headers: responseHeaders,
|
|
89
|
-
body: chunk,
|
|
90
|
-
chunkIndex: i,
|
|
91
|
-
isFinal,
|
|
92
|
-
chunked: true,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
sendResponse({
|
|
98
|
-
requestId,
|
|
99
|
-
statusCode,
|
|
100
|
-
headers: responseHeaders,
|
|
101
|
-
body: bodyBase64,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
(0, debug_1.debugLog)('TUNNEL', `Error: ${error.message}`);
|
|
107
|
-
sendResponse({
|
|
108
|
-
requestId,
|
|
109
|
-
statusCode: 502,
|
|
110
|
-
headers: { 'content-type': 'text/plain' },
|
|
111
|
-
body: Buffer.from(`Bad Gateway: ${error.message}`).toString('base64'),
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
this.activeRequests--;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
getBase64ChunkSize() {
|
|
119
|
-
// base64 encoding expands data by ~4/3, so chunk size in base64 chars
|
|
120
|
-
// for 200KB raw data is approximately 200*1024*4/3 ≈ 273KB base64
|
|
121
|
-
return Math.ceil(this.CHUNK_SIZE * 4 / 3);
|
|
122
|
-
}
|
|
123
|
-
makeHttpRequest(method, path, headers, body) {
|
|
124
|
-
return new Promise((resolve, reject) => {
|
|
125
|
-
// Build headers, removing hop-by-hop headers that shouldn't be forwarded
|
|
126
|
-
const forwardHeaders = {
|
|
127
|
-
...headers,
|
|
128
|
-
host: `${this.host}:${this.port}`,
|
|
129
|
-
};
|
|
130
|
-
delete forwardHeaders['connection'];
|
|
131
|
-
delete forwardHeaders['keep-alive'];
|
|
132
|
-
delete forwardHeaders['transfer-encoding'];
|
|
133
|
-
const options = {
|
|
134
|
-
hostname: this.host,
|
|
135
|
-
port: this.port,
|
|
136
|
-
path,
|
|
137
|
-
method,
|
|
138
|
-
headers: forwardHeaders,
|
|
139
|
-
timeout: 30000,
|
|
140
|
-
};
|
|
141
|
-
const req = http.request(options, (res) => {
|
|
142
|
-
const chunks = [];
|
|
143
|
-
let totalSize = 0;
|
|
144
|
-
res.on('data', (chunk) => {
|
|
145
|
-
totalSize += chunk.length;
|
|
146
|
-
if (totalSize > this.MAX_RESPONSE_SIZE) {
|
|
147
|
-
req.destroy();
|
|
148
|
-
reject(new Error(`Response exceeds maximum size of ${this.MAX_RESPONSE_SIZE} bytes`));
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
chunks.push(chunk);
|
|
152
|
-
});
|
|
153
|
-
res.on('end', () => {
|
|
154
|
-
const responseHeaders = {};
|
|
155
|
-
for (const [key, value] of Object.entries(res.headers)) {
|
|
156
|
-
if (value) {
|
|
157
|
-
responseHeaders[key] = Array.isArray(value) ? value.join(', ') : value;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
resolve({
|
|
161
|
-
statusCode: res.statusCode || 502,
|
|
162
|
-
responseHeaders,
|
|
163
|
-
responseBody: Buffer.concat(chunks),
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
res.on('error', reject);
|
|
167
|
-
});
|
|
168
|
-
req.on('error', reject);
|
|
169
|
-
req.on('timeout', () => {
|
|
170
|
-
req.destroy();
|
|
171
|
-
reject(new Error('Request timed out'));
|
|
172
|
-
});
|
|
173
|
-
if (body) {
|
|
174
|
-
req.write(body);
|
|
175
|
-
}
|
|
176
|
-
req.end();
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
exports.TunnelHandler = TunnelHandler;
|