@ricsam/r5d-browser 0.0.136 → 0.0.138
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 +7 -33
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/browser-input.mjs +56 -1
- package/dist/mjs/cli-options.mjs +1 -0
- package/dist/mjs/main.mjs +25 -396
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/personal-diagnostics.mjs +24 -0
- package/dist/mjs/personal-main.mjs +159 -0
- package/dist/mjs/personal-port-forward.mjs +183 -0
- package/dist/mjs/personal-relay-client.mjs +33 -0
- package/dist/mjs/personal-relay-shutdown.mjs +46 -0
- package/dist/mjs/runtime/browser-executor.mjs +11 -0
- package/dist/mjs/runtime/chromium-factory.mjs +101 -0
- package/dist/mjs/runtime/ipc.mjs +128 -0
- package/dist/mjs/runtime/isolated-root.mjs +53 -0
- package/dist/mjs/runtime/keeper.mjs +169 -0
- package/dist/mjs/runtime/ledger.mjs +193 -0
- package/dist/mjs/runtime/main.mjs +105 -0
- package/dist/mjs/runtime/private-paths.mjs +48 -0
- package/dist/mjs/runtime/protocol.mjs +72 -0
- package/dist/types/browser-input.d.ts +4 -0
- package/dist/types/cli-options.d.ts +1 -0
- package/dist/types/personal-diagnostics.d.ts +13 -0
- package/dist/types/personal-main.d.ts +9 -0
- package/dist/types/personal-port-forward.d.ts +45 -0
- package/dist/types/personal-relay-client.d.ts +51 -0
- package/dist/types/personal-relay-shutdown.d.ts +19 -0
- package/dist/types/runtime/browser-executor.d.ts +8 -0
- package/dist/types/runtime/chromium-factory.d.ts +25 -0
- package/dist/types/runtime/fixtures/counter-main.d.ts +1 -0
- package/dist/types/runtime/fixtures/counter-runtime.d.ts +7 -0
- package/dist/types/runtime/ipc.d.ts +23 -0
- package/dist/types/runtime/isolated-root.d.ts +12 -0
- package/dist/types/runtime/keeper.d.ts +44 -0
- package/dist/types/runtime/ledger.d.ts +41 -0
- package/dist/types/runtime/main.d.ts +23 -0
- package/dist/types/runtime/private-paths.d.ts +4 -0
- package/dist/types/runtime/protocol.d.ts +228 -0
- package/package.json +6 -5
- package/dist/cjs/browser-input.cjs +0 -87
- package/dist/cjs/browser-runtime.cjs +0 -708
- package/dist/cjs/chrome-launcher.cjs +0 -701
- package/dist/cjs/cli-options.cjs +0 -95
- package/dist/cjs/main.cjs +0 -427
- package/dist/cjs/password-safe-snapshot.cjs +0 -150
- package/dist/cjs/port-forward-manager.cjs +0 -233
- package/dist/cjs/upload-store.cjs +0 -136
- package/dist/mjs/port-forward-manager.mjs +0 -199
- package/dist/types/port-forward-manager.d.ts +0 -23
|
@@ -1,233 +0,0 @@
|
|
|
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_manager_exports = {};
|
|
30
|
-
__export(port_forward_manager_exports, {
|
|
31
|
-
BrowserPortForwardManager: () => BrowserPortForwardManager
|
|
32
|
-
});
|
|
33
|
-
module.exports = __toCommonJS(port_forward_manager_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, instanceId, forwardId, relayConnectionId) {
|
|
40
|
-
const url = new URL("/browser/port-forward/ws", baseUrl);
|
|
41
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
42
|
-
url.searchParams.set("instance", instanceId);
|
|
43
|
-
url.searchParams.set("forward", forwardId);
|
|
44
|
-
url.searchParams.set("connection", relayConnectionId);
|
|
45
|
-
return url.toString();
|
|
46
|
-
}
|
|
47
|
-
async function listen(server, port, host, ipv6Only = false) {
|
|
48
|
-
await new Promise((resolve, reject) => {
|
|
49
|
-
const onError = (error) => {
|
|
50
|
-
server.off("listening", onListening);
|
|
51
|
-
reject(error);
|
|
52
|
-
};
|
|
53
|
-
const onListening = () => {
|
|
54
|
-
server.off("error", onError);
|
|
55
|
-
resolve();
|
|
56
|
-
};
|
|
57
|
-
server.once("error", onError);
|
|
58
|
-
server.once("listening", onListening);
|
|
59
|
-
server.listen({ port, host, ipv6Only, exclusive: true });
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
async function closeServer(server) {
|
|
63
|
-
if (!server.listening) return;
|
|
64
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
65
|
-
}
|
|
66
|
-
function rawDataBuffer(data) {
|
|
67
|
-
if (Buffer.isBuffer(data)) return data;
|
|
68
|
-
if (Array.isArray(data)) return Buffer.concat(data);
|
|
69
|
-
return Buffer.from(data);
|
|
70
|
-
}
|
|
71
|
-
class BrowserPortForwardManager {
|
|
72
|
-
constructor(baseUrl, token, instanceId) {
|
|
73
|
-
this.baseUrl = baseUrl;
|
|
74
|
-
this.token = token;
|
|
75
|
-
this.instanceId = instanceId;
|
|
76
|
-
}
|
|
77
|
-
baseUrl;
|
|
78
|
-
token;
|
|
79
|
-
instanceId;
|
|
80
|
-
forwards = /* @__PURE__ */ new Map();
|
|
81
|
-
forwardByBrowserPort = /* @__PURE__ */ new Map();
|
|
82
|
-
operationQueue = Promise.resolve();
|
|
83
|
-
list() {
|
|
84
|
-
return [...this.forwards.values()].map(({ definition }) => ({ ...definition }));
|
|
85
|
-
}
|
|
86
|
-
start(definition) {
|
|
87
|
-
return this.enqueue(() => this.startNow(definition));
|
|
88
|
-
}
|
|
89
|
-
stop(forwardId) {
|
|
90
|
-
return this.enqueue(() => this.stopNow(forwardId));
|
|
91
|
-
}
|
|
92
|
-
close() {
|
|
93
|
-
return this.enqueue(async () => {
|
|
94
|
-
for (const forwardId of [...this.forwards.keys()]) await this.stopNow(forwardId);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
enqueue(operation) {
|
|
98
|
-
const result = this.operationQueue.then(operation, operation);
|
|
99
|
-
this.operationQueue = result.then(
|
|
100
|
-
() => void 0,
|
|
101
|
-
() => void 0
|
|
102
|
-
);
|
|
103
|
-
return result;
|
|
104
|
-
}
|
|
105
|
-
async startNow(definition) {
|
|
106
|
-
const existing = this.forwards.get(definition.forwardId);
|
|
107
|
-
if (existing) {
|
|
108
|
-
if (JSON.stringify(existing.definition) === JSON.stringify(definition)) return;
|
|
109
|
-
throw new Error(`Port forward ${definition.forwardId} already exists with different settings.`);
|
|
110
|
-
}
|
|
111
|
-
const occupyingId = this.forwardByBrowserPort.get(definition.browserPort);
|
|
112
|
-
if (occupyingId) throw new Error(`Browser port ${definition.browserPort} is already used by forward ${occupyingId}.`);
|
|
113
|
-
const active = { definition: { ...definition }, servers: [], relays: /* @__PURE__ */ new Set() };
|
|
114
|
-
const accept = (socket) => this.acceptConnection(active, socket);
|
|
115
|
-
const ipv4 = import_node_net.default.createServer({ allowHalfOpen: true }, accept);
|
|
116
|
-
const ipv6 = import_node_net.default.createServer({ allowHalfOpen: true }, accept);
|
|
117
|
-
const servers = [ipv4, ipv6];
|
|
118
|
-
active.servers = servers;
|
|
119
|
-
try {
|
|
120
|
-
await listen(ipv4, definition.browserPort, "127.0.0.1");
|
|
121
|
-
await listen(ipv6, definition.browserPort, "::1", true);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
await Promise.all(servers.map(closeServer));
|
|
124
|
-
throw new Error(
|
|
125
|
-
`Could not bind localhost:${definition.browserPort}: ${error instanceof Error ? error.message : String(error)}`
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
this.forwards.set(definition.forwardId, active);
|
|
129
|
-
this.forwardByBrowserPort.set(definition.browserPort, definition.forwardId);
|
|
130
|
-
}
|
|
131
|
-
async stopNow(forwardId) {
|
|
132
|
-
const active = this.forwards.get(forwardId);
|
|
133
|
-
if (!active) return;
|
|
134
|
-
this.forwards.delete(forwardId);
|
|
135
|
-
this.forwardByBrowserPort.delete(active.definition.browserPort);
|
|
136
|
-
for (const connection of active.relays) {
|
|
137
|
-
connection.socket.destroy();
|
|
138
|
-
connection.relay.close(1e3, "Port forward stopped");
|
|
139
|
-
}
|
|
140
|
-
active.relays.clear();
|
|
141
|
-
await Promise.all(active.servers.map(closeServer));
|
|
142
|
-
}
|
|
143
|
-
acceptConnection(active, socket) {
|
|
144
|
-
socket.pause();
|
|
145
|
-
socket.setNoDelay(true);
|
|
146
|
-
const relayConnectionId = crypto.randomUUID();
|
|
147
|
-
const relay = new import_ws.default(relayUrl(this.baseUrl, this.instanceId, active.definition.forwardId, relayConnectionId), {
|
|
148
|
-
headers: { Authorization: `Bearer ${this.token}` },
|
|
149
|
-
perMessageDeflate: false
|
|
150
|
-
});
|
|
151
|
-
const connection = { socket, relay };
|
|
152
|
-
active.relays.add(connection);
|
|
153
|
-
let ready = false;
|
|
154
|
-
const pendingChunks = [];
|
|
155
|
-
let pendingBytes = 0;
|
|
156
|
-
let resumeTimer;
|
|
157
|
-
let closing = false;
|
|
158
|
-
const cleanup = () => {
|
|
159
|
-
if (closing) return;
|
|
160
|
-
closing = true;
|
|
161
|
-
if (resumeTimer) clearInterval(resumeTimer);
|
|
162
|
-
active.relays.delete(connection);
|
|
163
|
-
socket.destroy();
|
|
164
|
-
if (relay.readyState === import_ws.default.OPEN || relay.readyState === import_ws.default.CONNECTING) relay.close();
|
|
165
|
-
};
|
|
166
|
-
const waitForRelayDrain = () => {
|
|
167
|
-
if (resumeTimer || relay.bufferedAmount <= RELAY_PAUSE_BYTES) return;
|
|
168
|
-
socket.pause();
|
|
169
|
-
resumeTimer = setInterval(() => {
|
|
170
|
-
if (relay.readyState !== import_ws.default.OPEN) return cleanup();
|
|
171
|
-
if (relay.bufferedAmount > RELAY_RESUME_BYTES) return;
|
|
172
|
-
clearInterval(resumeTimer);
|
|
173
|
-
resumeTimer = void 0;
|
|
174
|
-
if (ready) socket.resume();
|
|
175
|
-
}, 10);
|
|
176
|
-
};
|
|
177
|
-
const sendChunk = (chunk) => {
|
|
178
|
-
for (let offset = 0; offset < chunk.byteLength; offset += RELAY_FRAME_BYTES) {
|
|
179
|
-
relay.send(chunk.subarray(offset, Math.min(chunk.byteLength, offset + RELAY_FRAME_BYTES)), { binary: true });
|
|
180
|
-
}
|
|
181
|
-
waitForRelayDrain();
|
|
182
|
-
};
|
|
183
|
-
relay.on("message", (data, isBinary) => {
|
|
184
|
-
if (!isBinary) {
|
|
185
|
-
let control;
|
|
186
|
-
try {
|
|
187
|
-
control = JSON.parse(rawDataBuffer(data).toString("utf8"));
|
|
188
|
-
} catch {
|
|
189
|
-
return cleanup();
|
|
190
|
-
}
|
|
191
|
-
if (control.type === "ready") {
|
|
192
|
-
ready = true;
|
|
193
|
-
for (const chunk of pendingChunks.splice(0)) sendChunk(chunk);
|
|
194
|
-
pendingBytes = 0;
|
|
195
|
-
socket.resume();
|
|
196
|
-
} else if (control.type === "end") {
|
|
197
|
-
socket.end();
|
|
198
|
-
} else if (control.type === "error") {
|
|
199
|
-
socket.destroy(new Error(control.error || "Port-forward relay failed."));
|
|
200
|
-
}
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (!socket.write(rawDataBuffer(data))) {
|
|
204
|
-
relay.pause();
|
|
205
|
-
socket.once("drain", () => relay.resume());
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
relay.on("error", cleanup);
|
|
209
|
-
relay.once("close", cleanup);
|
|
210
|
-
socket.on("data", (chunk) => {
|
|
211
|
-
if (!ready || relay.readyState !== import_ws.default.OPEN) {
|
|
212
|
-
pendingBytes += chunk.byteLength;
|
|
213
|
-
if (pendingBytes > RELAY_PAUSE_BYTES) return cleanup();
|
|
214
|
-
pendingChunks.push(Buffer.from(chunk));
|
|
215
|
-
socket.pause();
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
sendChunk(chunk);
|
|
219
|
-
});
|
|
220
|
-
socket.on("end", () => {
|
|
221
|
-
if (relay.readyState === import_ws.default.OPEN) relay.send(JSON.stringify({ type: "end" }));
|
|
222
|
-
});
|
|
223
|
-
socket.on("error", (error) => {
|
|
224
|
-
if (relay.readyState === import_ws.default.OPEN) relay.send(JSON.stringify({ type: "error", error: error.message }));
|
|
225
|
-
});
|
|
226
|
-
socket.on("close", cleanup);
|
|
227
|
-
socket.pause();
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
231
|
-
0 && (module.exports = {
|
|
232
|
-
BrowserPortForwardManager
|
|
233
|
-
});
|
|
@@ -1,136 +0,0 @@
|
|
|
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 upload_store_exports = {};
|
|
30
|
-
__export(upload_store_exports, {
|
|
31
|
-
BrowserUploadStore: () => BrowserUploadStore
|
|
32
|
-
});
|
|
33
|
-
module.exports = __toCommonJS(upload_store_exports);
|
|
34
|
-
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
35
|
-
var import_node_path = __toESM(require("node:path"), 1);
|
|
36
|
-
var import_node_crypto = require("node:crypto");
|
|
37
|
-
var import_browser_upload = require("@ricsam/r5d-api/browser-upload");
|
|
38
|
-
class BrowserUploadStore {
|
|
39
|
-
constructor(root, releaseTarget = () => void 0) {
|
|
40
|
-
this.releaseTarget = releaseTarget;
|
|
41
|
-
import_node_fs.default.mkdirSync(root, { recursive: true, mode: 448 });
|
|
42
|
-
this.directory = import_node_fs.default.mkdtempSync(import_node_path.default.join(root, "run-"));
|
|
43
|
-
}
|
|
44
|
-
releaseTarget;
|
|
45
|
-
directory;
|
|
46
|
-
uploads = /* @__PURE__ */ new Map();
|
|
47
|
-
begin(id, tabId, files, target) {
|
|
48
|
-
if (typeof id !== "string" || !/^[a-f0-9-]{36}$/.test(id) || this.uploads.has(id)) throw new Error("Invalid or duplicate upload id.");
|
|
49
|
-
if (!Array.isArray(files) || files.length === 0 || files.length > 100) throw new Error("Upload requires between 1 and 100 files.");
|
|
50
|
-
const metadata = files.map(import_browser_upload.validateUploadFile);
|
|
51
|
-
const directory = import_node_fs.default.mkdtempSync(import_node_path.default.join(this.directory, "upload-"));
|
|
52
|
-
try {
|
|
53
|
-
const staged = metadata.map((file, index) => {
|
|
54
|
-
const parent = import_node_path.default.join(directory, String(index));
|
|
55
|
-
import_node_fs.default.mkdirSync(parent, { mode: 448 });
|
|
56
|
-
const filePath = import_node_path.default.join(parent, file.filename);
|
|
57
|
-
import_node_fs.default.closeSync(import_node_fs.default.openSync(filePath, "wx", 384));
|
|
58
|
-
return { ...file, path: filePath, offset: 0, hash: (0, import_node_crypto.createHash)("sha256") };
|
|
59
|
-
});
|
|
60
|
-
const timer = setTimeout(() => this.abort(id, tabId), 5 * 60 * 1e3);
|
|
61
|
-
timer.unref();
|
|
62
|
-
this.uploads.set(id, { tabId, directory, files: staged, target, attached: false, timer });
|
|
63
|
-
} catch (error) {
|
|
64
|
-
import_node_fs.default.rmSync(directory, { recursive: true, force: true });
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
get(id, tabId) {
|
|
69
|
-
const upload = typeof id === "string" ? this.uploads.get(id) : void 0;
|
|
70
|
-
if (!upload || upload.tabId !== tabId) throw new Error("Upload is missing, expired, or belongs to another tab.");
|
|
71
|
-
if (upload.attached) throw new Error("Upload has already been attached.");
|
|
72
|
-
upload.timer.refresh();
|
|
73
|
-
return upload;
|
|
74
|
-
}
|
|
75
|
-
append(id, tabId, index, offset, base64) {
|
|
76
|
-
const upload = this.get(id, tabId);
|
|
77
|
-
const file = typeof index === "number" && Number.isInteger(index) ? upload.files[index] : void 0;
|
|
78
|
-
if (!file || offset !== file.offset) throw new Error("Invalid upload file index or chunk offset.");
|
|
79
|
-
const bytes = (0, import_browser_upload.decodeUploadChunk)(base64);
|
|
80
|
-
if (bytes.length === 0 || file.offset + bytes.length > file.size)
|
|
81
|
-
throw new Error("Upload chunk does not match the declared file size.");
|
|
82
|
-
const fd = import_node_fs.default.openSync(file.path, import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_NOFOLLOW);
|
|
83
|
-
try {
|
|
84
|
-
let written = 0;
|
|
85
|
-
while (written < bytes.length) {
|
|
86
|
-
const count = import_node_fs.default.writeSync(fd, bytes, written, bytes.length - written, file.offset + written);
|
|
87
|
-
if (!count) throw new Error("Could not write upload chunk.");
|
|
88
|
-
written += count;
|
|
89
|
-
}
|
|
90
|
-
} finally {
|
|
91
|
-
import_node_fs.default.closeSync(fd);
|
|
92
|
-
}
|
|
93
|
-
file.hash.update(bytes);
|
|
94
|
-
file.offset += bytes.length;
|
|
95
|
-
}
|
|
96
|
-
ready(id, tabId, hashes) {
|
|
97
|
-
const upload = this.get(id, tabId);
|
|
98
|
-
if (!Array.isArray(hashes) || hashes.length !== upload.files.length) throw new Error("Upload requires a checksum for every file.");
|
|
99
|
-
for (const [index, file] of upload.files.entries()) {
|
|
100
|
-
if (file.offset !== file.size || file.hash.copy().digest("hex") !== hashes[index])
|
|
101
|
-
throw new Error("Upload is incomplete or its checksum does not match.");
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
target: upload.target,
|
|
105
|
-
paths: upload.files.map((file) => file.path),
|
|
106
|
-
files: upload.files.map(({ filename, size }) => ({ filename, size }))
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
attached(id, tabId) {
|
|
110
|
-
const upload = this.get(id, tabId);
|
|
111
|
-
upload.attached = true;
|
|
112
|
-
clearTimeout(upload.timer);
|
|
113
|
-
}
|
|
114
|
-
abort(id, tabId) {
|
|
115
|
-
const upload = this.uploads.get(id);
|
|
116
|
-
if (!upload || upload.tabId !== tabId || upload.attached) return;
|
|
117
|
-
this.remove(id, upload);
|
|
118
|
-
}
|
|
119
|
-
remove(id, upload) {
|
|
120
|
-
clearTimeout(upload.timer);
|
|
121
|
-
this.uploads.delete(id);
|
|
122
|
-
this.releaseTarget(upload.target);
|
|
123
|
-
import_node_fs.default.rmSync(upload.directory, { recursive: true, force: true });
|
|
124
|
-
}
|
|
125
|
-
closeTab(tabId) {
|
|
126
|
-
for (const [id, upload] of this.uploads) if (upload.tabId === tabId) this.remove(id, upload);
|
|
127
|
-
}
|
|
128
|
-
close() {
|
|
129
|
-
for (const [id, upload] of this.uploads) this.remove(id, upload);
|
|
130
|
-
import_node_fs.default.rmSync(this.directory, { recursive: true, force: true });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
134
|
-
0 && (module.exports = {
|
|
135
|
-
BrowserUploadStore
|
|
136
|
-
});
|
|
@@ -1,199 +0,0 @@
|
|
|
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, instanceId, forwardId, relayConnectionId) {
|
|
7
|
-
const url = new URL("/browser/port-forward/ws", baseUrl);
|
|
8
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
9
|
-
url.searchParams.set("instance", instanceId);
|
|
10
|
-
url.searchParams.set("forward", forwardId);
|
|
11
|
-
url.searchParams.set("connection", relayConnectionId);
|
|
12
|
-
return url.toString();
|
|
13
|
-
}
|
|
14
|
-
async function listen(server, port, host, ipv6Only = false) {
|
|
15
|
-
await new Promise((resolve, reject) => {
|
|
16
|
-
const onError = (error) => {
|
|
17
|
-
server.off("listening", onListening);
|
|
18
|
-
reject(error);
|
|
19
|
-
};
|
|
20
|
-
const onListening = () => {
|
|
21
|
-
server.off("error", onError);
|
|
22
|
-
resolve();
|
|
23
|
-
};
|
|
24
|
-
server.once("error", onError);
|
|
25
|
-
server.once("listening", onListening);
|
|
26
|
-
server.listen({ port, host, ipv6Only, exclusive: true });
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
async function closeServer(server) {
|
|
30
|
-
if (!server.listening) return;
|
|
31
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
32
|
-
}
|
|
33
|
-
function rawDataBuffer(data) {
|
|
34
|
-
if (Buffer.isBuffer(data)) return data;
|
|
35
|
-
if (Array.isArray(data)) return Buffer.concat(data);
|
|
36
|
-
return Buffer.from(data);
|
|
37
|
-
}
|
|
38
|
-
class BrowserPortForwardManager {
|
|
39
|
-
constructor(baseUrl, token, instanceId) {
|
|
40
|
-
this.baseUrl = baseUrl;
|
|
41
|
-
this.token = token;
|
|
42
|
-
this.instanceId = instanceId;
|
|
43
|
-
}
|
|
44
|
-
baseUrl;
|
|
45
|
-
token;
|
|
46
|
-
instanceId;
|
|
47
|
-
forwards = /* @__PURE__ */ new Map();
|
|
48
|
-
forwardByBrowserPort = /* @__PURE__ */ new Map();
|
|
49
|
-
operationQueue = Promise.resolve();
|
|
50
|
-
list() {
|
|
51
|
-
return [...this.forwards.values()].map(({ definition }) => ({ ...definition }));
|
|
52
|
-
}
|
|
53
|
-
start(definition) {
|
|
54
|
-
return this.enqueue(() => this.startNow(definition));
|
|
55
|
-
}
|
|
56
|
-
stop(forwardId) {
|
|
57
|
-
return this.enqueue(() => this.stopNow(forwardId));
|
|
58
|
-
}
|
|
59
|
-
close() {
|
|
60
|
-
return this.enqueue(async () => {
|
|
61
|
-
for (const forwardId of [...this.forwards.keys()]) await this.stopNow(forwardId);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
enqueue(operation) {
|
|
65
|
-
const result = this.operationQueue.then(operation, operation);
|
|
66
|
-
this.operationQueue = result.then(
|
|
67
|
-
() => void 0,
|
|
68
|
-
() => void 0
|
|
69
|
-
);
|
|
70
|
-
return result;
|
|
71
|
-
}
|
|
72
|
-
async startNow(definition) {
|
|
73
|
-
const existing = this.forwards.get(definition.forwardId);
|
|
74
|
-
if (existing) {
|
|
75
|
-
if (JSON.stringify(existing.definition) === JSON.stringify(definition)) return;
|
|
76
|
-
throw new Error(`Port forward ${definition.forwardId} already exists with different settings.`);
|
|
77
|
-
}
|
|
78
|
-
const occupyingId = this.forwardByBrowserPort.get(definition.browserPort);
|
|
79
|
-
if (occupyingId) throw new Error(`Browser port ${definition.browserPort} is already used by forward ${occupyingId}.`);
|
|
80
|
-
const active = { definition: { ...definition }, servers: [], relays: /* @__PURE__ */ new Set() };
|
|
81
|
-
const accept = (socket) => this.acceptConnection(active, socket);
|
|
82
|
-
const ipv4 = net.createServer({ allowHalfOpen: true }, accept);
|
|
83
|
-
const ipv6 = net.createServer({ allowHalfOpen: true }, accept);
|
|
84
|
-
const servers = [ipv4, ipv6];
|
|
85
|
-
active.servers = servers;
|
|
86
|
-
try {
|
|
87
|
-
await listen(ipv4, definition.browserPort, "127.0.0.1");
|
|
88
|
-
await listen(ipv6, definition.browserPort, "::1", true);
|
|
89
|
-
} catch (error) {
|
|
90
|
-
await Promise.all(servers.map(closeServer));
|
|
91
|
-
throw new Error(
|
|
92
|
-
`Could not bind localhost:${definition.browserPort}: ${error instanceof Error ? error.message : String(error)}`
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
this.forwards.set(definition.forwardId, active);
|
|
96
|
-
this.forwardByBrowserPort.set(definition.browserPort, definition.forwardId);
|
|
97
|
-
}
|
|
98
|
-
async stopNow(forwardId) {
|
|
99
|
-
const active = this.forwards.get(forwardId);
|
|
100
|
-
if (!active) return;
|
|
101
|
-
this.forwards.delete(forwardId);
|
|
102
|
-
this.forwardByBrowserPort.delete(active.definition.browserPort);
|
|
103
|
-
for (const connection of active.relays) {
|
|
104
|
-
connection.socket.destroy();
|
|
105
|
-
connection.relay.close(1e3, "Port forward stopped");
|
|
106
|
-
}
|
|
107
|
-
active.relays.clear();
|
|
108
|
-
await Promise.all(active.servers.map(closeServer));
|
|
109
|
-
}
|
|
110
|
-
acceptConnection(active, socket) {
|
|
111
|
-
socket.pause();
|
|
112
|
-
socket.setNoDelay(true);
|
|
113
|
-
const relayConnectionId = crypto.randomUUID();
|
|
114
|
-
const relay = new WebSocket(relayUrl(this.baseUrl, this.instanceId, active.definition.forwardId, relayConnectionId), {
|
|
115
|
-
headers: { Authorization: `Bearer ${this.token}` },
|
|
116
|
-
perMessageDeflate: false
|
|
117
|
-
});
|
|
118
|
-
const connection = { socket, relay };
|
|
119
|
-
active.relays.add(connection);
|
|
120
|
-
let ready = false;
|
|
121
|
-
const pendingChunks = [];
|
|
122
|
-
let pendingBytes = 0;
|
|
123
|
-
let resumeTimer;
|
|
124
|
-
let closing = false;
|
|
125
|
-
const cleanup = () => {
|
|
126
|
-
if (closing) return;
|
|
127
|
-
closing = true;
|
|
128
|
-
if (resumeTimer) clearInterval(resumeTimer);
|
|
129
|
-
active.relays.delete(connection);
|
|
130
|
-
socket.destroy();
|
|
131
|
-
if (relay.readyState === WebSocket.OPEN || relay.readyState === WebSocket.CONNECTING) relay.close();
|
|
132
|
-
};
|
|
133
|
-
const waitForRelayDrain = () => {
|
|
134
|
-
if (resumeTimer || relay.bufferedAmount <= RELAY_PAUSE_BYTES) return;
|
|
135
|
-
socket.pause();
|
|
136
|
-
resumeTimer = setInterval(() => {
|
|
137
|
-
if (relay.readyState !== WebSocket.OPEN) return cleanup();
|
|
138
|
-
if (relay.bufferedAmount > RELAY_RESUME_BYTES) return;
|
|
139
|
-
clearInterval(resumeTimer);
|
|
140
|
-
resumeTimer = void 0;
|
|
141
|
-
if (ready) socket.resume();
|
|
142
|
-
}, 10);
|
|
143
|
-
};
|
|
144
|
-
const sendChunk = (chunk) => {
|
|
145
|
-
for (let offset = 0; offset < chunk.byteLength; offset += RELAY_FRAME_BYTES) {
|
|
146
|
-
relay.send(chunk.subarray(offset, Math.min(chunk.byteLength, offset + RELAY_FRAME_BYTES)), { binary: true });
|
|
147
|
-
}
|
|
148
|
-
waitForRelayDrain();
|
|
149
|
-
};
|
|
150
|
-
relay.on("message", (data, isBinary) => {
|
|
151
|
-
if (!isBinary) {
|
|
152
|
-
let control;
|
|
153
|
-
try {
|
|
154
|
-
control = JSON.parse(rawDataBuffer(data).toString("utf8"));
|
|
155
|
-
} catch {
|
|
156
|
-
return cleanup();
|
|
157
|
-
}
|
|
158
|
-
if (control.type === "ready") {
|
|
159
|
-
ready = true;
|
|
160
|
-
for (const chunk of pendingChunks.splice(0)) sendChunk(chunk);
|
|
161
|
-
pendingBytes = 0;
|
|
162
|
-
socket.resume();
|
|
163
|
-
} else if (control.type === "end") {
|
|
164
|
-
socket.end();
|
|
165
|
-
} else if (control.type === "error") {
|
|
166
|
-
socket.destroy(new Error(control.error || "Port-forward relay failed."));
|
|
167
|
-
}
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
if (!socket.write(rawDataBuffer(data))) {
|
|
171
|
-
relay.pause();
|
|
172
|
-
socket.once("drain", () => relay.resume());
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
relay.on("error", cleanup);
|
|
176
|
-
relay.once("close", cleanup);
|
|
177
|
-
socket.on("data", (chunk) => {
|
|
178
|
-
if (!ready || relay.readyState !== WebSocket.OPEN) {
|
|
179
|
-
pendingBytes += chunk.byteLength;
|
|
180
|
-
if (pendingBytes > RELAY_PAUSE_BYTES) return cleanup();
|
|
181
|
-
pendingChunks.push(Buffer.from(chunk));
|
|
182
|
-
socket.pause();
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
sendChunk(chunk);
|
|
186
|
-
});
|
|
187
|
-
socket.on("end", () => {
|
|
188
|
-
if (relay.readyState === WebSocket.OPEN) relay.send(JSON.stringify({ type: "end" }));
|
|
189
|
-
});
|
|
190
|
-
socket.on("error", (error) => {
|
|
191
|
-
if (relay.readyState === WebSocket.OPEN) relay.send(JSON.stringify({ type: "error", error: error.message }));
|
|
192
|
-
});
|
|
193
|
-
socket.on("close", cleanup);
|
|
194
|
-
socket.pause();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
export {
|
|
198
|
-
BrowserPortForwardManager
|
|
199
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export type BrowserPortForwardDefinition = {
|
|
2
|
-
forwardId: string;
|
|
3
|
-
workerLabel: string;
|
|
4
|
-
workerPort: number;
|
|
5
|
-
browserPort: number;
|
|
6
|
-
};
|
|
7
|
-
export declare class BrowserPortForwardManager {
|
|
8
|
-
private readonly baseUrl;
|
|
9
|
-
private readonly token;
|
|
10
|
-
private readonly instanceId;
|
|
11
|
-
private readonly forwards;
|
|
12
|
-
private readonly forwardByBrowserPort;
|
|
13
|
-
private operationQueue;
|
|
14
|
-
constructor(baseUrl: string, token: string, instanceId: string);
|
|
15
|
-
list(): BrowserPortForwardDefinition[];
|
|
16
|
-
start(definition: BrowserPortForwardDefinition): Promise<void>;
|
|
17
|
-
stop(forwardId: string): Promise<void>;
|
|
18
|
-
close(): Promise<void>;
|
|
19
|
-
private enqueue;
|
|
20
|
-
private startNow;
|
|
21
|
-
private stopNow;
|
|
22
|
-
private acceptConnection;
|
|
23
|
-
}
|