amalgm 0.1.62 → 0.1.63

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.
@@ -231,6 +231,7 @@ function createEventTunnel({ record, foreground = false }) {
231
231
  let connectStartedAt = 0;
232
232
  let lastGatewayFrameAt = 0;
233
233
  const upstreamSockets = new Map();
234
+ const upstreamStreams = new Map();
234
235
 
235
236
  function log(message) {
236
237
  const line = `[event-tunnel] ${message}`;
@@ -386,6 +387,71 @@ function createEventTunnel({ record, foreground = false }) {
386
387
  req.end(body);
387
388
  }
388
389
 
390
+ function forwardStream(frame) {
391
+ const targetPort = resolveTargetPort(frame);
392
+ if (!targetPort) {
393
+ send({
394
+ type: 'stream_res_error',
395
+ req_id: frame.req_id,
396
+ status: 403,
397
+ message: 'Target port is not allowed',
398
+ });
399
+ return;
400
+ }
401
+ const body = frame.body_b64 ? Buffer.from(frame.body_b64, 'base64') : Buffer.alloc(0);
402
+ const req = http.request(
403
+ {
404
+ hostname: '127.0.0.1',
405
+ port: targetPort,
406
+ method: frame.method || 'GET',
407
+ path: frame.path || '/',
408
+ headers: localHeaders(frame.headers),
409
+ },
410
+ (res) => {
411
+ send({
412
+ type: 'stream_res_start',
413
+ req_id: frame.req_id,
414
+ status: res.statusCode || 200,
415
+ headers: responseHeaders(res.headers),
416
+ });
417
+ res.on('data', (chunk) => {
418
+ send({
419
+ type: 'stream_res_data',
420
+ req_id: frame.req_id,
421
+ chunk_b64: Buffer.from(chunk).toString('base64'),
422
+ });
423
+ });
424
+ res.on('end', () => {
425
+ upstreamStreams.delete(frame.req_id);
426
+ send({ type: 'stream_res_end', req_id: frame.req_id });
427
+ });
428
+ },
429
+ );
430
+
431
+ upstreamStreams.set(frame.req_id, req);
432
+
433
+ req.on('error', (error) => {
434
+ if (!upstreamStreams.has(frame.req_id)) return;
435
+ upstreamStreams.delete(frame.req_id);
436
+ send({
437
+ type: 'stream_res_error',
438
+ req_id: frame.req_id,
439
+ status: 502,
440
+ message: error.message || 'Local stream request failed',
441
+ });
442
+ });
443
+
444
+ if (body.length > 0) req.write(body);
445
+ req.end();
446
+ }
447
+
448
+ function cancelStream(frame) {
449
+ const req = upstreamStreams.get(frame.req_id);
450
+ if (!req) return;
451
+ upstreamStreams.delete(frame.req_id);
452
+ req.destroy(new Error('stream canceled'));
453
+ }
454
+
389
455
  function openPreviewSocket(frame) {
390
456
  const targetPort = resolveTargetPort(frame);
391
457
  if (!targetPort) {
@@ -436,6 +502,14 @@ function createEventTunnel({ record, foreground = false }) {
436
502
  handleRequest(frame);
437
503
  return;
438
504
  }
505
+ if (frame.type === 'stream_req') {
506
+ forwardStream(frame);
507
+ return;
508
+ }
509
+ if (frame.type === 'stream_cancel') {
510
+ cancelStream(frame);
511
+ return;
512
+ }
439
513
  if (frame.type === 'ws_open') {
440
514
  openPreviewSocket(frame);
441
515
  return;
@@ -518,6 +592,14 @@ function createEventTunnel({ record, foreground = false }) {
518
592
  }
519
593
  }
520
594
  upstreamSockets.clear();
595
+ for (const stream of upstreamStreams.values()) {
596
+ try {
597
+ stream.destroy(new Error('event tunnel stopped'));
598
+ } catch {
599
+ // noop
600
+ }
601
+ }
602
+ upstreamStreams.clear();
521
603
  void postPresence(record, false);
522
604
  if (ws) {
523
605
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.62",
3
+ "version": "0.1.63",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,