flowscale 2.1.0-beta.5 → 2.1.0-beta.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/dist/operator-server.d.ts +19 -9
- package/dist/operator-server.js +258 -51
- package/package.json +1 -1
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import type { NodePodsTransport } from './node-transport';
|
|
2
2
|
/**
|
|
3
|
-
* PodsOperatorServer — exposes a
|
|
4
|
-
*
|
|
5
|
-
* without any direct knowledge of ComfyUI.
|
|
3
|
+
* PodsOperatorServer — exposes a complete ComfyUI proxy API so that remote
|
|
4
|
+
* FlowScale apps (e.g. AI OS, Studio) can operate entirely through the operator
|
|
5
|
+
* without any direct knowledge of ComfyUI ports or URLs.
|
|
6
6
|
*
|
|
7
|
-
* Endpoints:
|
|
8
|
-
* GET /api/pods
|
|
9
|
-
* GET /api/pods/:id
|
|
10
|
-
* POST /api/pods/:id/upload
|
|
11
|
-
* POST /api/pods/:id/execute
|
|
12
|
-
* GET /api/pods/:id/view?filename=
|
|
7
|
+
* HTTP Endpoints:
|
|
8
|
+
* GET /api/pods — list all pods
|
|
9
|
+
* GET /api/pods/:id — get a single pod
|
|
10
|
+
* POST /api/pods/:id/upload — upload a file to ComfyUI
|
|
11
|
+
* POST /api/pods/:id/execute — execute workflow and wait (blocking)
|
|
12
|
+
* GET /api/pods/:id/view?filename= — proxy a ComfyUI output image
|
|
13
|
+
* GET /api/pods/:id/workflow?filename= — load a workflow JSON from ComfyUI userdata
|
|
14
|
+
* POST /api/pods/:id/queue — queue a prompt (non-blocking, returns prompt_id)
|
|
15
|
+
* GET /api/pods/:id/history/:promptId — fetch execution history for a prompt
|
|
16
|
+
* GET /api/pods/:id/object_info — proxy ComfyUI node type definitions
|
|
17
|
+
* POST /api/pods/:id/interrupt — cancel current execution
|
|
18
|
+
*
|
|
19
|
+
* WebSocket:
|
|
20
|
+
* WS /api/pods/:id/ws?clientId= — transparent tunnel to ComfyUI WS
|
|
13
21
|
*
|
|
14
22
|
* Serves on 0.0.0.0 so any machine on the LAN can reach it.
|
|
15
23
|
* Includes CORS headers for browser-based clients.
|
|
@@ -23,6 +31,8 @@ export declare class PodsOperatorServer {
|
|
|
23
31
|
start(): void;
|
|
24
32
|
stop(): void;
|
|
25
33
|
private _json;
|
|
34
|
+
/** Pipe a GET response from ComfyUI directly to the client (handles large payloads). */
|
|
35
|
+
private _proxyGet;
|
|
26
36
|
private _readBody;
|
|
27
37
|
/** Returns the port of the first running instance in the given pod. */
|
|
28
38
|
private _runningPort;
|
package/dist/operator-server.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
2
13
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
14
|
if (k2 === undefined) k2 = k;
|
|
4
15
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -71,18 +82,27 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
71
82
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
72
83
|
exports.PodsOperatorServer = void 0;
|
|
73
84
|
var http = __importStar(require("http"));
|
|
85
|
+
var net = __importStar(require("net"));
|
|
74
86
|
var local_pods_1 = require("./local-pods");
|
|
75
87
|
/**
|
|
76
|
-
* PodsOperatorServer — exposes a
|
|
77
|
-
*
|
|
78
|
-
* without any direct knowledge of ComfyUI.
|
|
88
|
+
* PodsOperatorServer — exposes a complete ComfyUI proxy API so that remote
|
|
89
|
+
* FlowScale apps (e.g. AI OS, Studio) can operate entirely through the operator
|
|
90
|
+
* without any direct knowledge of ComfyUI ports or URLs.
|
|
91
|
+
*
|
|
92
|
+
* HTTP Endpoints:
|
|
93
|
+
* GET /api/pods — list all pods
|
|
94
|
+
* GET /api/pods/:id — get a single pod
|
|
95
|
+
* POST /api/pods/:id/upload — upload a file to ComfyUI
|
|
96
|
+
* POST /api/pods/:id/execute — execute workflow and wait (blocking)
|
|
97
|
+
* GET /api/pods/:id/view?filename= — proxy a ComfyUI output image
|
|
98
|
+
* GET /api/pods/:id/workflow?filename= — load a workflow JSON from ComfyUI userdata
|
|
99
|
+
* POST /api/pods/:id/queue — queue a prompt (non-blocking, returns prompt_id)
|
|
100
|
+
* GET /api/pods/:id/history/:promptId — fetch execution history for a prompt
|
|
101
|
+
* GET /api/pods/:id/object_info — proxy ComfyUI node type definitions
|
|
102
|
+
* POST /api/pods/:id/interrupt — cancel current execution
|
|
79
103
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* GET /api/pods/:id — get a single pod
|
|
83
|
-
* POST /api/pods/:id/upload — upload a file to the pod's ComfyUI instance
|
|
84
|
-
* POST /api/pods/:id/execute — execute a workflow and wait for outputs
|
|
85
|
-
* GET /api/pods/:id/view?filename= — proxy a ComfyUI output image
|
|
104
|
+
* WebSocket:
|
|
105
|
+
* WS /api/pods/:id/ws?clientId= — transparent tunnel to ComfyUI WS
|
|
86
106
|
*
|
|
87
107
|
* Serves on 0.0.0.0 so any machine on the LAN can reach it.
|
|
88
108
|
* Includes CORS headers for browser-based clients.
|
|
@@ -100,11 +120,11 @@ var PodsOperatorServer = /** @class */ (function () {
|
|
|
100
120
|
if (this.server)
|
|
101
121
|
return;
|
|
102
122
|
this.server = http.createServer(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
103
|
-
var url, query, pods, podMatch, pod, uploadMatch, podId, port_1, executeMatch, podId, body, _a, _b, result, viewMatch, podId, port,
|
|
123
|
+
var url, query, pods, podMatch, pod, uploadMatch, podId, port_1, executeMatch, podId, body, _a, _b, result, viewMatch, podId, port, workflowMatch, podId, port, filename, encoded, queueMatch, podId, port_2, body_1, historyMatch, podId, promptId, port, objectInfoMatch, podId, port, interruptMatch, podId, port_3, catchAllMatch, podId, subpath, port_4, qs, fullPath_1, err_1;
|
|
104
124
|
var _this = this;
|
|
105
|
-
var _c, _d, _e, _f, _g, _h;
|
|
106
|
-
return __generator(this, function (
|
|
107
|
-
switch (
|
|
125
|
+
var _c, _d, _e, _f, _g, _h, _j;
|
|
126
|
+
return __generator(this, function (_k) {
|
|
127
|
+
switch (_k.label) {
|
|
108
128
|
case 0:
|
|
109
129
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
110
130
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
@@ -116,20 +136,20 @@ var PodsOperatorServer = /** @class */ (function () {
|
|
|
116
136
|
}
|
|
117
137
|
url = ((_c = req.url) !== null && _c !== void 0 ? _c : '/').split('?')[0];
|
|
118
138
|
query = new URLSearchParams((_e = ((_d = req.url) !== null && _d !== void 0 ? _d : '').split('?')[1]) !== null && _e !== void 0 ? _e : '');
|
|
119
|
-
|
|
139
|
+
_k.label = 1;
|
|
120
140
|
case 1:
|
|
121
|
-
|
|
141
|
+
_k.trys.push([1, 36, , 37]);
|
|
122
142
|
if (!(req.method === 'GET' && (url === '/api/pods' || url === '/api/pods/'))) return [3 /*break*/, 3];
|
|
123
143
|
return [4 /*yield*/, this.transport.list()];
|
|
124
144
|
case 2:
|
|
125
|
-
pods =
|
|
145
|
+
pods = _k.sent();
|
|
126
146
|
return [2 /*return*/, this._json(res, 200, pods)];
|
|
127
147
|
case 3:
|
|
128
148
|
podMatch = url.match(/^\/api\/pods\/([^/]+)$/);
|
|
129
149
|
if (!(req.method === 'GET' && podMatch)) return [3 /*break*/, 5];
|
|
130
150
|
return [4 /*yield*/, this.transport.get(decodeURIComponent(podMatch[1]))];
|
|
131
151
|
case 4:
|
|
132
|
-
pod =
|
|
152
|
+
pod = _k.sent();
|
|
133
153
|
if (!pod)
|
|
134
154
|
return [2 /*return*/, this._json(res, 404, { error: 'Pod not found' })];
|
|
135
155
|
return [2 /*return*/, this._json(res, 200, pod)];
|
|
@@ -137,12 +157,9 @@ var PodsOperatorServer = /** @class */ (function () {
|
|
|
137
157
|
uploadMatch = url.match(/^\/api\/pods\/([^/]+)\/upload$/);
|
|
138
158
|
if (!(req.method === 'POST' && uploadMatch)) return [3 /*break*/, 8];
|
|
139
159
|
podId = decodeURIComponent(uploadMatch[1]);
|
|
140
|
-
return [4 /*yield*/, this._runningPort(podId)
|
|
141
|
-
// Pipe the multipart body directly to ComfyUI to preserve boundary and encoding
|
|
142
|
-
];
|
|
160
|
+
return [4 /*yield*/, this._runningPort(podId)];
|
|
143
161
|
case 6:
|
|
144
|
-
port_1 =
|
|
145
|
-
// Pipe the multipart body directly to ComfyUI to preserve boundary and encoding
|
|
162
|
+
port_1 = _k.sent();
|
|
146
163
|
return [4 /*yield*/, new Promise(function (resolve, reject) {
|
|
147
164
|
var _a;
|
|
148
165
|
var headers = {
|
|
@@ -172,8 +189,7 @@ var PodsOperatorServer = /** @class */ (function () {
|
|
|
172
189
|
req.pipe(upstreamReq);
|
|
173
190
|
})];
|
|
174
191
|
case 7:
|
|
175
|
-
|
|
176
|
-
_j.sent();
|
|
192
|
+
_k.sent();
|
|
177
193
|
return [2 /*return*/];
|
|
178
194
|
case 8:
|
|
179
195
|
executeMatch = url.match(/^\/api\/pods\/([^/]+)\/execute$/);
|
|
@@ -182,46 +198,220 @@ var PodsOperatorServer = /** @class */ (function () {
|
|
|
182
198
|
_b = (_a = JSON).parse;
|
|
183
199
|
return [4 /*yield*/, this._readBody(req)];
|
|
184
200
|
case 9:
|
|
185
|
-
body = _b.apply(_a, [(
|
|
201
|
+
body = _b.apply(_a, [(_k.sent()).toString()]);
|
|
186
202
|
return [4 /*yield*/, this.api.executeWorkflowAndWait(podId, body.workflow, (_f = body.options) !== null && _f !== void 0 ? _f : {})];
|
|
187
203
|
case 10:
|
|
188
|
-
result =
|
|
204
|
+
result = _k.sent();
|
|
189
205
|
return [2 /*return*/, this._json(res, 200, result)];
|
|
190
206
|
case 11:
|
|
191
207
|
viewMatch = url.match(/^\/api\/pods\/([^/]+)\/view$/);
|
|
192
|
-
if (!(req.method === 'GET' && viewMatch)) return [3 /*break*/,
|
|
208
|
+
if (!(req.method === 'GET' && viewMatch)) return [3 /*break*/, 14];
|
|
193
209
|
podId = decodeURIComponent(viewMatch[1]);
|
|
194
210
|
return [4 /*yield*/, this._runningPort(podId)];
|
|
195
211
|
case 12:
|
|
196
|
-
port =
|
|
197
|
-
|
|
198
|
-
return [4 /*yield*/, fetch("http://localhost:".concat(port, "/view?").concat(params))];
|
|
212
|
+
port = _k.sent();
|
|
213
|
+
return [4 /*yield*/, this._proxyGet(res, port, "/view?".concat(query))];
|
|
199
214
|
case 13:
|
|
200
|
-
|
|
201
|
-
if (!upstream.ok) {
|
|
202
|
-
res.writeHead(upstream.status);
|
|
203
|
-
res.end();
|
|
204
|
-
return [2 /*return*/];
|
|
205
|
-
}
|
|
206
|
-
return [4 /*yield*/, upstream.arrayBuffer()];
|
|
207
|
-
case 14:
|
|
208
|
-
imageBuffer = _j.sent();
|
|
209
|
-
res.writeHead(200, {
|
|
210
|
-
'Content-Type': (_g = upstream.headers.get('content-type')) !== null && _g !== void 0 ? _g : 'image/png',
|
|
211
|
-
'Cache-Control': 'public, max-age=3600',
|
|
212
|
-
});
|
|
213
|
-
res.end(Buffer.from(imageBuffer));
|
|
215
|
+
_k.sent();
|
|
214
216
|
return [2 /*return*/];
|
|
217
|
+
case 14:
|
|
218
|
+
workflowMatch = url.match(/^\/api\/pods\/([^/]+)\/workflow$/);
|
|
219
|
+
if (!(req.method === 'GET' && workflowMatch)) return [3 /*break*/, 17];
|
|
220
|
+
podId = decodeURIComponent(workflowMatch[1]);
|
|
221
|
+
return [4 /*yield*/, this._runningPort(podId)];
|
|
215
222
|
case 15:
|
|
223
|
+
port = _k.sent();
|
|
224
|
+
filename = (_g = query.get('filename')) !== null && _g !== void 0 ? _g : '';
|
|
225
|
+
encoded = encodeURIComponent("workflows/".concat(filename));
|
|
226
|
+
return [4 /*yield*/, this._proxyGet(res, port, "/api/userdata/".concat(encoded))];
|
|
227
|
+
case 16:
|
|
228
|
+
_k.sent();
|
|
229
|
+
return [2 /*return*/];
|
|
230
|
+
case 17:
|
|
231
|
+
queueMatch = url.match(/^\/api\/pods\/([^/]+)\/queue$/);
|
|
232
|
+
if (!(req.method === 'POST' && queueMatch)) return [3 /*break*/, 21];
|
|
233
|
+
podId = decodeURIComponent(queueMatch[1]);
|
|
234
|
+
return [4 /*yield*/, this._runningPort(podId)];
|
|
235
|
+
case 18:
|
|
236
|
+
port_2 = _k.sent();
|
|
237
|
+
return [4 /*yield*/, this._readBody(req)];
|
|
238
|
+
case 19:
|
|
239
|
+
body_1 = _k.sent();
|
|
240
|
+
return [4 /*yield*/, new Promise(function (resolve, reject) {
|
|
241
|
+
var upstreamReq = http.request({
|
|
242
|
+
hostname: 'localhost',
|
|
243
|
+
port: port_2,
|
|
244
|
+
path: '/prompt', method: 'POST',
|
|
245
|
+
headers: { 'content-type': 'application/json', 'content-length': body_1.length },
|
|
246
|
+
}, function (upstreamRes) {
|
|
247
|
+
var chunks = [];
|
|
248
|
+
upstreamRes.on('data', function (c) { return chunks.push(c); });
|
|
249
|
+
upstreamRes.on('end', function () {
|
|
250
|
+
var _a;
|
|
251
|
+
try {
|
|
252
|
+
var data = JSON.parse(Buffer.concat(chunks).toString());
|
|
253
|
+
_this._json(res, (_a = upstreamRes.statusCode) !== null && _a !== void 0 ? _a : 500, data);
|
|
254
|
+
resolve();
|
|
255
|
+
}
|
|
256
|
+
catch (e) {
|
|
257
|
+
reject(e);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
upstreamRes.on('error', reject);
|
|
261
|
+
});
|
|
262
|
+
upstreamReq.on('error', reject);
|
|
263
|
+
upstreamReq.write(body_1);
|
|
264
|
+
upstreamReq.end();
|
|
265
|
+
})];
|
|
266
|
+
case 20:
|
|
267
|
+
_k.sent();
|
|
268
|
+
return [2 /*return*/];
|
|
269
|
+
case 21:
|
|
270
|
+
historyMatch = url.match(/^\/api\/pods\/([^/]+)\/history\/([^/]+)$/);
|
|
271
|
+
if (!(req.method === 'GET' && historyMatch)) return [3 /*break*/, 24];
|
|
272
|
+
podId = decodeURIComponent(historyMatch[1]);
|
|
273
|
+
promptId = decodeURIComponent(historyMatch[2]);
|
|
274
|
+
return [4 /*yield*/, this._runningPort(podId)];
|
|
275
|
+
case 22:
|
|
276
|
+
port = _k.sent();
|
|
277
|
+
return [4 /*yield*/, this._proxyGet(res, port, "/history/".concat(promptId))];
|
|
278
|
+
case 23:
|
|
279
|
+
_k.sent();
|
|
280
|
+
return [2 /*return*/];
|
|
281
|
+
case 24:
|
|
282
|
+
objectInfoMatch = url.match(/^\/api\/pods\/([^/]+)\/object_info$/);
|
|
283
|
+
if (!(req.method === 'GET' && objectInfoMatch)) return [3 /*break*/, 27];
|
|
284
|
+
podId = decodeURIComponent(objectInfoMatch[1]);
|
|
285
|
+
return [4 /*yield*/, this._runningPort(podId)];
|
|
286
|
+
case 25:
|
|
287
|
+
port = _k.sent();
|
|
288
|
+
return [4 /*yield*/, this._proxyGet(res, port, '/object_info')];
|
|
289
|
+
case 26:
|
|
290
|
+
_k.sent();
|
|
291
|
+
return [2 /*return*/];
|
|
292
|
+
case 27:
|
|
293
|
+
interruptMatch = url.match(/^\/api\/pods\/([^/]+)\/interrupt$/);
|
|
294
|
+
if (!(req.method === 'POST' && interruptMatch)) return [3 /*break*/, 30];
|
|
295
|
+
podId = decodeURIComponent(interruptMatch[1]);
|
|
296
|
+
return [4 /*yield*/, this._runningPort(podId)];
|
|
297
|
+
case 28:
|
|
298
|
+
port_3 = _k.sent();
|
|
299
|
+
return [4 /*yield*/, new Promise(function (resolve, reject) {
|
|
300
|
+
var upstreamReq = http.request({ hostname: 'localhost', port: port_3, path: '/interrupt', method: 'POST' }, function (upstreamRes) { upstreamRes.resume(); upstreamRes.on('end', resolve); });
|
|
301
|
+
upstreamReq.on('error', reject);
|
|
302
|
+
upstreamReq.end();
|
|
303
|
+
})];
|
|
304
|
+
case 29:
|
|
305
|
+
_k.sent();
|
|
306
|
+
return [2 /*return*/, this._json(res, 200, { ok: true })];
|
|
307
|
+
case 30:
|
|
308
|
+
catchAllMatch = url.match(/^\/api\/pods\/([^/]+)(\/.+)$/);
|
|
309
|
+
if (!catchAllMatch) return [3 /*break*/, 35];
|
|
310
|
+
podId = decodeURIComponent(catchAllMatch[1]);
|
|
311
|
+
subpath = catchAllMatch[2];
|
|
312
|
+
return [4 /*yield*/, this._runningPort(podId)];
|
|
313
|
+
case 31:
|
|
314
|
+
port_4 = _k.sent();
|
|
315
|
+
qs = ((_h = req.url) !== null && _h !== void 0 ? _h : '').split('?')[1];
|
|
316
|
+
fullPath_1 = qs ? "".concat(subpath, "?").concat(qs) : subpath;
|
|
317
|
+
if (!(req.method === 'GET' || req.method === 'HEAD')) return [3 /*break*/, 33];
|
|
318
|
+
return [4 /*yield*/, this._proxyGet(res, port_4, fullPath_1)];
|
|
319
|
+
case 32:
|
|
320
|
+
_k.sent();
|
|
321
|
+
return [2 /*return*/];
|
|
322
|
+
case 33:
|
|
323
|
+
// POST / PUT / PATCH / DELETE — pipe body through
|
|
324
|
+
return [4 /*yield*/, new Promise(function (resolve, reject) {
|
|
325
|
+
var upstream = http.request({
|
|
326
|
+
hostname: 'localhost',
|
|
327
|
+
port: port_4,
|
|
328
|
+
path: fullPath_1, method: req.method,
|
|
329
|
+
headers: __assign(__assign({}, (req.headers['content-type'] ? { 'content-type': req.headers['content-type'] } : {})), (req.headers['content-length'] ? { 'content-length': req.headers['content-length'] } : {})),
|
|
330
|
+
}, function (upstreamRes) {
|
|
331
|
+
var _a, _b;
|
|
332
|
+
res.writeHead((_a = upstreamRes.statusCode) !== null && _a !== void 0 ? _a : 200, {
|
|
333
|
+
'Content-Type': (_b = upstreamRes.headers['content-type']) !== null && _b !== void 0 ? _b : 'application/json',
|
|
334
|
+
'Access-Control-Allow-Origin': '*',
|
|
335
|
+
});
|
|
336
|
+
upstreamRes.pipe(res);
|
|
337
|
+
upstreamRes.on('end', resolve);
|
|
338
|
+
upstreamRes.on('error', reject);
|
|
339
|
+
});
|
|
340
|
+
upstream.on('error', reject);
|
|
341
|
+
req.pipe(upstream);
|
|
342
|
+
})];
|
|
343
|
+
case 34:
|
|
344
|
+
// POST / PUT / PATCH / DELETE — pipe body through
|
|
345
|
+
_k.sent();
|
|
346
|
+
return [2 /*return*/];
|
|
347
|
+
case 35:
|
|
216
348
|
res.writeHead(404);
|
|
217
349
|
res.end('Not found');
|
|
218
|
-
return [3 /*break*/,
|
|
219
|
-
case
|
|
220
|
-
err_1 =
|
|
350
|
+
return [3 /*break*/, 37];
|
|
351
|
+
case 36:
|
|
352
|
+
err_1 = _k.sent();
|
|
221
353
|
console.error('[PodsOperatorServer] Error handling request:', err_1);
|
|
222
|
-
this._json(res, 500, { error: (
|
|
223
|
-
return [3 /*break*/,
|
|
224
|
-
case
|
|
354
|
+
this._json(res, 500, { error: (_j = err_1 === null || err_1 === void 0 ? void 0 : err_1.message) !== null && _j !== void 0 ? _j : 'Internal server error' });
|
|
355
|
+
return [3 /*break*/, 37];
|
|
356
|
+
case 37: return [2 /*return*/];
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}); });
|
|
360
|
+
// ── WebSocket tunnel: WS /api/pods/:id/ws?clientId= ─────────────────────
|
|
361
|
+
this.server.on('upgrade', function (req, socket, head) { return __awaiter(_this, void 0, void 0, function () {
|
|
362
|
+
var rawUrl, urlPath, qs, wsMatch, podId, port_5, clientId_1, upstream_1, err_2;
|
|
363
|
+
var _a, _b, _c;
|
|
364
|
+
return __generator(this, function (_d) {
|
|
365
|
+
switch (_d.label) {
|
|
366
|
+
case 0:
|
|
367
|
+
rawUrl = (_a = req.url) !== null && _a !== void 0 ? _a : '/';
|
|
368
|
+
urlPath = rawUrl.split('?')[0];
|
|
369
|
+
qs = new URLSearchParams((_b = rawUrl.split('?')[1]) !== null && _b !== void 0 ? _b : '');
|
|
370
|
+
wsMatch = urlPath.match(/^\/api\/pods\/([^/]+)\/ws$/);
|
|
371
|
+
if (!wsMatch) {
|
|
372
|
+
socket.destroy();
|
|
373
|
+
return [2 /*return*/];
|
|
374
|
+
}
|
|
375
|
+
podId = decodeURIComponent(wsMatch[1]);
|
|
376
|
+
_d.label = 1;
|
|
377
|
+
case 1:
|
|
378
|
+
_d.trys.push([1, 3, , 4]);
|
|
379
|
+
return [4 /*yield*/, this._runningPort(podId)];
|
|
380
|
+
case 2:
|
|
381
|
+
port_5 = _d.sent();
|
|
382
|
+
clientId_1 = (_c = qs.get('clientId')) !== null && _c !== void 0 ? _c : '';
|
|
383
|
+
upstream_1 = net.createConnection({ host: 'localhost', port: port_5 });
|
|
384
|
+
upstream_1.on('connect', function () {
|
|
385
|
+
var _a, _b;
|
|
386
|
+
var upgradeReq = [
|
|
387
|
+
"GET /ws?clientId=".concat(encodeURIComponent(clientId_1), " HTTP/1.1"),
|
|
388
|
+
"Host: localhost:".concat(port_5),
|
|
389
|
+
'Upgrade: websocket',
|
|
390
|
+
'Connection: Upgrade',
|
|
391
|
+
"Sec-WebSocket-Key: ".concat((_a = req.headers['sec-websocket-key']) !== null && _a !== void 0 ? _a : ''),
|
|
392
|
+
"Sec-WebSocket-Version: ".concat((_b = req.headers['sec-websocket-version']) !== null && _b !== void 0 ? _b : '13'),
|
|
393
|
+
'',
|
|
394
|
+
'',
|
|
395
|
+
].join('\r\n');
|
|
396
|
+
upstream_1.write(upgradeReq);
|
|
397
|
+
if (head && head.length > 0)
|
|
398
|
+
upstream_1.write(head);
|
|
399
|
+
// Bidirectional pipe — all WS frames pass through transparently
|
|
400
|
+
socket.pipe(upstream_1);
|
|
401
|
+
upstream_1.pipe(socket);
|
|
402
|
+
});
|
|
403
|
+
upstream_1.on('error', function () { return socket.destroy(); });
|
|
404
|
+
socket.on('error', function () { return upstream_1.destroy(); });
|
|
405
|
+
socket.on('close', function () { return upstream_1.destroy(); });
|
|
406
|
+
upstream_1.on('close', function () { return socket.destroy(); });
|
|
407
|
+
return [3 /*break*/, 4];
|
|
408
|
+
case 3:
|
|
409
|
+
err_2 = _d.sent();
|
|
410
|
+
console.error('[PodsOperatorServer] WS tunnel error:', err_2);
|
|
411
|
+
socket.write('HTTP/1.1 503 Service Unavailable\r\n\r\n');
|
|
412
|
+
socket.destroy();
|
|
413
|
+
return [3 /*break*/, 4];
|
|
414
|
+
case 4: return [2 /*return*/];
|
|
225
415
|
}
|
|
226
416
|
});
|
|
227
417
|
}); });
|
|
@@ -240,9 +430,26 @@ var PodsOperatorServer = /** @class */ (function () {
|
|
|
240
430
|
};
|
|
241
431
|
// ── Private helpers ──────────────────────────────────────────────────────
|
|
242
432
|
PodsOperatorServer.prototype._json = function (res, status, data) {
|
|
243
|
-
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
433
|
+
res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
244
434
|
res.end(JSON.stringify(data));
|
|
245
435
|
};
|
|
436
|
+
/** Pipe a GET response from ComfyUI directly to the client (handles large payloads). */
|
|
437
|
+
PodsOperatorServer.prototype._proxyGet = function (res, port, path) {
|
|
438
|
+
return new Promise(function (resolve, reject) {
|
|
439
|
+
var upstreamReq = http.request({ hostname: 'localhost', port: port, path: path, method: 'GET' }, function (upstreamRes) {
|
|
440
|
+
var _a, _b;
|
|
441
|
+
res.writeHead((_a = upstreamRes.statusCode) !== null && _a !== void 0 ? _a : 200, {
|
|
442
|
+
'Content-Type': (_b = upstreamRes.headers['content-type']) !== null && _b !== void 0 ? _b : 'application/json',
|
|
443
|
+
'Access-Control-Allow-Origin': '*',
|
|
444
|
+
});
|
|
445
|
+
upstreamRes.pipe(res);
|
|
446
|
+
upstreamRes.on('end', resolve);
|
|
447
|
+
upstreamRes.on('error', reject);
|
|
448
|
+
});
|
|
449
|
+
upstreamReq.on('error', reject);
|
|
450
|
+
upstreamReq.end();
|
|
451
|
+
});
|
|
452
|
+
};
|
|
246
453
|
PodsOperatorServer.prototype._readBody = function (req) {
|
|
247
454
|
return new Promise(function (resolve, reject) {
|
|
248
455
|
var chunks = [];
|