@ricsam/r5d-worker 0.0.47 → 0.0.48
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/README.md +2 -0
- package/dist/cjs/main.cjs +14 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/port-forward-client.cjs +148 -0
- package/dist/mjs/main.mjs +14 -1
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/port-forward-client.mjs +114 -0
- package/dist/types/port-forward-client.d.ts +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,3 +37,5 @@ The agent runs commands through the virtual shell command:
|
|
|
37
37
|
worker exec macos docker compose up -d
|
|
38
38
|
worker exec macos bun test
|
|
39
39
|
```
|
|
40
|
+
|
|
41
|
+
When the connected `r5d-browser` requests a port forward, the worker opens each relayed connection only to `127.0.0.1` on the requested worker port. Browser-side and worker-side ports may differ. The worker never opens a public listener, and a disconnected worker leaves the browser's long-lived mapping unavailable until the same worker label reconnects.
|
package/dist/cjs/main.cjs
CHANGED
|
@@ -58,6 +58,7 @@ var import_cli_update = require("./cli-update.cjs");
|
|
|
58
58
|
var import_git_identity = require("./git-identity.cjs");
|
|
59
59
|
var import_heartbeat = require("./heartbeat.cjs");
|
|
60
60
|
var import_process_tree = require("./process-tree.cjs");
|
|
61
|
+
var import_port_forward_client = require("./port-forward-client.cjs");
|
|
61
62
|
var import_supervisor = require("./supervisor.cjs");
|
|
62
63
|
var import_managed_paths = require("./managed-paths.cjs");
|
|
63
64
|
var import_workspace_sync = require("./workspace-sync.cjs");
|
|
@@ -2568,7 +2569,8 @@ async function startWorker(options) {
|
|
|
2568
2569
|
r5dctlVersion: (0, import_cli_update.readInstalledCliVersion)("r5dctl"),
|
|
2569
2570
|
capabilities: {
|
|
2570
2571
|
updateClis: true,
|
|
2571
|
-
canonicalResolverCheckout: true
|
|
2572
|
+
canonicalResolverCheckout: true,
|
|
2573
|
+
browserPortForwarding: true
|
|
2572
2574
|
},
|
|
2573
2575
|
projectRoot: projectsRoot,
|
|
2574
2576
|
artifactRoot,
|
|
@@ -2721,6 +2723,17 @@ async function startWorker(options) {
|
|
|
2721
2723
|
pendingProcessTerminals.delete(message.runId);
|
|
2722
2724
|
return;
|
|
2723
2725
|
}
|
|
2726
|
+
if (message.type === "port_forward_connect") {
|
|
2727
|
+
(0, import_port_forward_client.openWorkerPortForwardRelay)({
|
|
2728
|
+
baseUrl,
|
|
2729
|
+
token,
|
|
2730
|
+
label,
|
|
2731
|
+
forwardId: message.forwardId,
|
|
2732
|
+
relayConnectionId: message.relayConnectionId,
|
|
2733
|
+
workerPort: message.workerPort
|
|
2734
|
+
});
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2724
2737
|
if (message.type === "update_clis") {
|
|
2725
2738
|
if (cliUpdateInProgress) {
|
|
2726
2739
|
sendWorkerMessage(ws, {
|
package/dist/cjs/package.json
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var port_forward_client_exports = {};
|
|
30
|
+
__export(port_forward_client_exports, {
|
|
31
|
+
openWorkerPortForwardRelay: () => openWorkerPortForwardRelay
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(port_forward_client_exports);
|
|
34
|
+
var import_node_net = __toESM(require("node:net"), 1);
|
|
35
|
+
var import_ws = __toESM(require("ws"), 1);
|
|
36
|
+
const RELAY_FRAME_BYTES = 64 * 1024;
|
|
37
|
+
const RELAY_PAUSE_BYTES = 1024 * 1024;
|
|
38
|
+
const RELAY_RESUME_BYTES = 256 * 1024;
|
|
39
|
+
function relayUrl(baseUrl, label, forwardId, relayConnectionId) {
|
|
40
|
+
const url = new URL("/worker/port-forward/ws", baseUrl);
|
|
41
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
42
|
+
url.searchParams.set("label", label);
|
|
43
|
+
url.searchParams.set("forward", forwardId);
|
|
44
|
+
url.searchParams.set("connection", relayConnectionId);
|
|
45
|
+
return url.toString();
|
|
46
|
+
}
|
|
47
|
+
function rawDataBuffer(data) {
|
|
48
|
+
if (Buffer.isBuffer(data)) return data;
|
|
49
|
+
if (Array.isArray(data)) return Buffer.concat(data);
|
|
50
|
+
return Buffer.from(data);
|
|
51
|
+
}
|
|
52
|
+
function openWorkerPortForwardRelay(options) {
|
|
53
|
+
const socket = import_node_net.default.createConnection({ host: "127.0.0.1", port: options.workerPort, allowHalfOpen: true });
|
|
54
|
+
socket.pause();
|
|
55
|
+
socket.setNoDelay(true);
|
|
56
|
+
const relay = new import_ws.default(relayUrl(options.baseUrl, options.label, options.forwardId, options.relayConnectionId), {
|
|
57
|
+
headers: { Authorization: `Bearer ${options.token}` },
|
|
58
|
+
perMessageDeflate: false
|
|
59
|
+
});
|
|
60
|
+
let relayReady = false;
|
|
61
|
+
let targetReady = false;
|
|
62
|
+
const pendingChunks = [];
|
|
63
|
+
let pendingBytes = 0;
|
|
64
|
+
let closing = false;
|
|
65
|
+
let resumeTimer;
|
|
66
|
+
const maybeResume = () => {
|
|
67
|
+
if (relayReady && targetReady && !resumeTimer) socket.resume();
|
|
68
|
+
};
|
|
69
|
+
const cleanup = () => {
|
|
70
|
+
if (closing) return;
|
|
71
|
+
closing = true;
|
|
72
|
+
if (resumeTimer) clearInterval(resumeTimer);
|
|
73
|
+
socket.destroy();
|
|
74
|
+
if (relay.readyState === import_ws.default.OPEN || relay.readyState === import_ws.default.CONNECTING) relay.close();
|
|
75
|
+
};
|
|
76
|
+
const sendError = (message) => {
|
|
77
|
+
if (relay.readyState === import_ws.default.OPEN) relay.send(JSON.stringify({ type: "error", error: message }));
|
|
78
|
+
cleanup();
|
|
79
|
+
};
|
|
80
|
+
const waitForRelayDrain = () => {
|
|
81
|
+
if (resumeTimer || relay.bufferedAmount <= RELAY_PAUSE_BYTES) return;
|
|
82
|
+
socket.pause();
|
|
83
|
+
resumeTimer = setInterval(() => {
|
|
84
|
+
if (relay.readyState !== import_ws.default.OPEN) return cleanup();
|
|
85
|
+
if (relay.bufferedAmount > RELAY_RESUME_BYTES) return;
|
|
86
|
+
clearInterval(resumeTimer);
|
|
87
|
+
resumeTimer = void 0;
|
|
88
|
+
maybeResume();
|
|
89
|
+
}, 10);
|
|
90
|
+
};
|
|
91
|
+
const sendChunk = (chunk) => {
|
|
92
|
+
for (let offset = 0; offset < chunk.byteLength; offset += RELAY_FRAME_BYTES) {
|
|
93
|
+
relay.send(chunk.subarray(offset, Math.min(chunk.byteLength, offset + RELAY_FRAME_BYTES)), { binary: true });
|
|
94
|
+
}
|
|
95
|
+
waitForRelayDrain();
|
|
96
|
+
};
|
|
97
|
+
socket.once("connect", () => {
|
|
98
|
+
targetReady = true;
|
|
99
|
+
maybeResume();
|
|
100
|
+
});
|
|
101
|
+
socket.on("data", (chunk) => {
|
|
102
|
+
if (!relayReady || relay.readyState !== import_ws.default.OPEN) {
|
|
103
|
+
pendingBytes += chunk.byteLength;
|
|
104
|
+
if (pendingBytes > RELAY_PAUSE_BYTES) return cleanup();
|
|
105
|
+
pendingChunks.push(Buffer.from(chunk));
|
|
106
|
+
socket.pause();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
sendChunk(chunk);
|
|
110
|
+
});
|
|
111
|
+
socket.on("end", () => {
|
|
112
|
+
if (relay.readyState === import_ws.default.OPEN) relay.send(JSON.stringify({ type: "end" }));
|
|
113
|
+
});
|
|
114
|
+
socket.on("error", (error) => sendError(`Could not connect to 127.0.0.1:${options.workerPort}: ${error.message}`));
|
|
115
|
+
socket.on("close", cleanup);
|
|
116
|
+
relay.on("message", (data, isBinary) => {
|
|
117
|
+
if (!isBinary) {
|
|
118
|
+
let control;
|
|
119
|
+
try {
|
|
120
|
+
control = JSON.parse(rawDataBuffer(data).toString("utf8"));
|
|
121
|
+
} catch {
|
|
122
|
+
return cleanup();
|
|
123
|
+
}
|
|
124
|
+
if (control.type === "ready") {
|
|
125
|
+
relayReady = true;
|
|
126
|
+
for (const chunk of pendingChunks.splice(0)) sendChunk(chunk);
|
|
127
|
+
pendingBytes = 0;
|
|
128
|
+
maybeResume();
|
|
129
|
+
} else if (control.type === "end") {
|
|
130
|
+
socket.end();
|
|
131
|
+
} else if (control.type === "error") {
|
|
132
|
+
socket.destroy(new Error(control.error || "Port-forward relay failed."));
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!socket.write(rawDataBuffer(data))) {
|
|
137
|
+
relay.pause();
|
|
138
|
+
socket.once("drain", () => relay.resume());
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
relay.once("error", cleanup);
|
|
142
|
+
relay.once("close", cleanup);
|
|
143
|
+
socket.pause();
|
|
144
|
+
}
|
|
145
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
146
|
+
0 && (module.exports = {
|
|
147
|
+
openWorkerPortForwardRelay
|
|
148
|
+
});
|
package/dist/mjs/main.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { installCliUpdate, readInstalledCliVersion } from "./cli-update.mjs";
|
|
|
8
8
|
import { configureVisibleGitIdentity } from "./git-identity.mjs";
|
|
9
9
|
import { hasWorkerHeartbeatTimedOut, WORKER_HEARTBEAT_INTERVAL_MS } from "./heartbeat.mjs";
|
|
10
10
|
import { terminateProcessTree } from "./process-tree.mjs";
|
|
11
|
+
import { openWorkerPortForwardRelay } from "./port-forward-client.mjs";
|
|
11
12
|
import {
|
|
12
13
|
isRetryableWorkerServerStatus,
|
|
13
14
|
superviseWorkerRuntime,
|
|
@@ -2530,7 +2531,8 @@ async function startWorker(options) {
|
|
|
2530
2531
|
r5dctlVersion: readInstalledCliVersion("r5dctl"),
|
|
2531
2532
|
capabilities: {
|
|
2532
2533
|
updateClis: true,
|
|
2533
|
-
canonicalResolverCheckout: true
|
|
2534
|
+
canonicalResolverCheckout: true,
|
|
2535
|
+
browserPortForwarding: true
|
|
2534
2536
|
},
|
|
2535
2537
|
projectRoot: projectsRoot,
|
|
2536
2538
|
artifactRoot,
|
|
@@ -2683,6 +2685,17 @@ async function startWorker(options) {
|
|
|
2683
2685
|
pendingProcessTerminals.delete(message.runId);
|
|
2684
2686
|
return;
|
|
2685
2687
|
}
|
|
2688
|
+
if (message.type === "port_forward_connect") {
|
|
2689
|
+
openWorkerPortForwardRelay({
|
|
2690
|
+
baseUrl,
|
|
2691
|
+
token,
|
|
2692
|
+
label,
|
|
2693
|
+
forwardId: message.forwardId,
|
|
2694
|
+
relayConnectionId: message.relayConnectionId,
|
|
2695
|
+
workerPort: message.workerPort
|
|
2696
|
+
});
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2686
2699
|
if (message.type === "update_clis") {
|
|
2687
2700
|
if (cliUpdateInProgress) {
|
|
2688
2701
|
sendWorkerMessage(ws, {
|
package/dist/mjs/package.json
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
import WebSocket, {} from "ws";
|
|
3
|
+
const RELAY_FRAME_BYTES = 64 * 1024;
|
|
4
|
+
const RELAY_PAUSE_BYTES = 1024 * 1024;
|
|
5
|
+
const RELAY_RESUME_BYTES = 256 * 1024;
|
|
6
|
+
function relayUrl(baseUrl, label, forwardId, relayConnectionId) {
|
|
7
|
+
const url = new URL("/worker/port-forward/ws", baseUrl);
|
|
8
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
9
|
+
url.searchParams.set("label", label);
|
|
10
|
+
url.searchParams.set("forward", forwardId);
|
|
11
|
+
url.searchParams.set("connection", relayConnectionId);
|
|
12
|
+
return url.toString();
|
|
13
|
+
}
|
|
14
|
+
function rawDataBuffer(data) {
|
|
15
|
+
if (Buffer.isBuffer(data)) return data;
|
|
16
|
+
if (Array.isArray(data)) return Buffer.concat(data);
|
|
17
|
+
return Buffer.from(data);
|
|
18
|
+
}
|
|
19
|
+
function openWorkerPortForwardRelay(options) {
|
|
20
|
+
const socket = net.createConnection({ host: "127.0.0.1", port: options.workerPort, allowHalfOpen: true });
|
|
21
|
+
socket.pause();
|
|
22
|
+
socket.setNoDelay(true);
|
|
23
|
+
const relay = new WebSocket(relayUrl(options.baseUrl, options.label, options.forwardId, options.relayConnectionId), {
|
|
24
|
+
headers: { Authorization: `Bearer ${options.token}` },
|
|
25
|
+
perMessageDeflate: false
|
|
26
|
+
});
|
|
27
|
+
let relayReady = false;
|
|
28
|
+
let targetReady = false;
|
|
29
|
+
const pendingChunks = [];
|
|
30
|
+
let pendingBytes = 0;
|
|
31
|
+
let closing = false;
|
|
32
|
+
let resumeTimer;
|
|
33
|
+
const maybeResume = () => {
|
|
34
|
+
if (relayReady && targetReady && !resumeTimer) socket.resume();
|
|
35
|
+
};
|
|
36
|
+
const cleanup = () => {
|
|
37
|
+
if (closing) return;
|
|
38
|
+
closing = true;
|
|
39
|
+
if (resumeTimer) clearInterval(resumeTimer);
|
|
40
|
+
socket.destroy();
|
|
41
|
+
if (relay.readyState === WebSocket.OPEN || relay.readyState === WebSocket.CONNECTING) relay.close();
|
|
42
|
+
};
|
|
43
|
+
const sendError = (message) => {
|
|
44
|
+
if (relay.readyState === WebSocket.OPEN) relay.send(JSON.stringify({ type: "error", error: message }));
|
|
45
|
+
cleanup();
|
|
46
|
+
};
|
|
47
|
+
const waitForRelayDrain = () => {
|
|
48
|
+
if (resumeTimer || relay.bufferedAmount <= RELAY_PAUSE_BYTES) return;
|
|
49
|
+
socket.pause();
|
|
50
|
+
resumeTimer = setInterval(() => {
|
|
51
|
+
if (relay.readyState !== WebSocket.OPEN) return cleanup();
|
|
52
|
+
if (relay.bufferedAmount > RELAY_RESUME_BYTES) return;
|
|
53
|
+
clearInterval(resumeTimer);
|
|
54
|
+
resumeTimer = void 0;
|
|
55
|
+
maybeResume();
|
|
56
|
+
}, 10);
|
|
57
|
+
};
|
|
58
|
+
const sendChunk = (chunk) => {
|
|
59
|
+
for (let offset = 0; offset < chunk.byteLength; offset += RELAY_FRAME_BYTES) {
|
|
60
|
+
relay.send(chunk.subarray(offset, Math.min(chunk.byteLength, offset + RELAY_FRAME_BYTES)), { binary: true });
|
|
61
|
+
}
|
|
62
|
+
waitForRelayDrain();
|
|
63
|
+
};
|
|
64
|
+
socket.once("connect", () => {
|
|
65
|
+
targetReady = true;
|
|
66
|
+
maybeResume();
|
|
67
|
+
});
|
|
68
|
+
socket.on("data", (chunk) => {
|
|
69
|
+
if (!relayReady || relay.readyState !== WebSocket.OPEN) {
|
|
70
|
+
pendingBytes += chunk.byteLength;
|
|
71
|
+
if (pendingBytes > RELAY_PAUSE_BYTES) return cleanup();
|
|
72
|
+
pendingChunks.push(Buffer.from(chunk));
|
|
73
|
+
socket.pause();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
sendChunk(chunk);
|
|
77
|
+
});
|
|
78
|
+
socket.on("end", () => {
|
|
79
|
+
if (relay.readyState === WebSocket.OPEN) relay.send(JSON.stringify({ type: "end" }));
|
|
80
|
+
});
|
|
81
|
+
socket.on("error", (error) => sendError(`Could not connect to 127.0.0.1:${options.workerPort}: ${error.message}`));
|
|
82
|
+
socket.on("close", cleanup);
|
|
83
|
+
relay.on("message", (data, isBinary) => {
|
|
84
|
+
if (!isBinary) {
|
|
85
|
+
let control;
|
|
86
|
+
try {
|
|
87
|
+
control = JSON.parse(rawDataBuffer(data).toString("utf8"));
|
|
88
|
+
} catch {
|
|
89
|
+
return cleanup();
|
|
90
|
+
}
|
|
91
|
+
if (control.type === "ready") {
|
|
92
|
+
relayReady = true;
|
|
93
|
+
for (const chunk of pendingChunks.splice(0)) sendChunk(chunk);
|
|
94
|
+
pendingBytes = 0;
|
|
95
|
+
maybeResume();
|
|
96
|
+
} else if (control.type === "end") {
|
|
97
|
+
socket.end();
|
|
98
|
+
} else if (control.type === "error") {
|
|
99
|
+
socket.destroy(new Error(control.error || "Port-forward relay failed."));
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!socket.write(rawDataBuffer(data))) {
|
|
104
|
+
relay.pause();
|
|
105
|
+
socket.once("drain", () => relay.resume());
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
relay.once("error", cleanup);
|
|
109
|
+
relay.once("close", cleanup);
|
|
110
|
+
socket.pause();
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
openWorkerPortForwardRelay
|
|
114
|
+
};
|