mindwire 0.1.30 → 0.1.33
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/{chunk-RN7ERVJ2.js → chunk-H2I3VRBS.js} +183 -8
- package/dist/chunk-H2I3VRBS.js.map +1 -0
- package/dist/cli.js +47 -10
- package/dist/cli.js.map +1 -1
- package/dist/computer/connection-progress.d.ts +14 -0
- package/dist/computer/lifecycle.d.ts +1 -0
- package/dist/computer/relay-health.d.ts +10 -0
- package/dist/computer/relay-output.d.ts +19 -0
- package/dist/computer/relay.d.ts +4 -1
- package/dist/{controller-DIJKMWWU.js → controller-QBC6L7AT.js} +199 -120
- package/dist/controller-QBC6L7AT.js.map +1 -0
- package/dist/{controller-handoff-4GXF4ATR.js → controller-handoff-NFDOLPJL.js} +3 -3
- package/dist/{controller-handoff-4GXF4ATR.js.map → controller-handoff-NFDOLPJL.js.map} +1 -1
- package/dist/index.cjs +177 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +175 -4
- package/dist/index.js.map +1 -1
- package/dist/project-sync.d.ts +101 -0
- package/dist/types.d.ts +4 -0
- package/dist/workspace.d.ts +27 -4
- package/package.json +1 -1
- package/dist/chunk-RN7ERVJ2.js.map +0 -1
- package/dist/controller-DIJKMWWU.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { binaryCacheDirectory, cachedExecutable, verifyReleaseBytes, cacheExecutable, executableChecksum } from './chunk-3FR7BFAW.js';
|
|
2
|
-
import { readJSON, processStateAlive, currentProcessIdentity, ensureDaemonBinary, SDK_VERSION, computerClient, processIdentity, adoptProcess, writeJSON, directRoutes, CONTROLLER_PROTOCOL, ownChild } from './chunk-
|
|
2
|
+
import { readJSON, processStateAlive, currentProcessIdentity, ensureDaemonBinary, SDK_VERSION, computerClient, processIdentity, adoptProcess, writeJSON, directRoutes, CONTROLLER_PROTOCOL, ownChild } from './chunk-H2I3VRBS.js';
|
|
3
3
|
import './chunk-T6TD76UA.js';
|
|
4
4
|
import { execFile, execFileSync, spawn } from 'child_process';
|
|
5
|
-
import { randomBytes } from 'crypto';
|
|
5
|
+
import { randomBytes, randomUUID } from 'crypto';
|
|
6
6
|
import * as fs from 'fs/promises';
|
|
7
|
-
import { readFile, mkdtemp, writeFile
|
|
8
|
-
import * as
|
|
7
|
+
import { open, rm, readFile, mkdtemp, writeFile } from 'fs/promises';
|
|
8
|
+
import * as path3 from 'path';
|
|
9
9
|
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
10
10
|
import { tmpdir } from 'os';
|
|
11
11
|
import { promisify } from 'util';
|
|
@@ -23,7 +23,7 @@ async function ensureCloudflareBinary(options = {}) {
|
|
|
23
23
|
throw new Error("Install cloudflared for this computer, or select another connection provider.");
|
|
24
24
|
}
|
|
25
25
|
const asset = `cloudflared-${platform === "win32" ? "windows" : platform}-${cpu}${platform === "darwin" ? ".tgz" : platform === "win32" ? ".exe" : ""}`;
|
|
26
|
-
const bin =
|
|
26
|
+
const bin = path3.join(
|
|
27
27
|
options.cacheDir ?? await binaryCacheDirectory(),
|
|
28
28
|
"cloudflared",
|
|
29
29
|
CLOUDFLARED_VERSION,
|
|
@@ -50,9 +50,9 @@ async function ensureCloudflareBinary(options = {}) {
|
|
|
50
50
|
await verifyReleaseBytes(archive, checksum, asset);
|
|
51
51
|
let executable = archive;
|
|
52
52
|
if (platform === "darwin") {
|
|
53
|
-
const temporary = await mkdtemp(
|
|
53
|
+
const temporary = await mkdtemp(path3.join(tmpdir(), "mindwire-cloudflared-"));
|
|
54
54
|
try {
|
|
55
|
-
const archivePath =
|
|
55
|
+
const archivePath = path3.join(temporary, asset);
|
|
56
56
|
await writeFile(archivePath, archive, { mode: 384 });
|
|
57
57
|
const result = await execute(
|
|
58
58
|
"/usr/bin/tar",
|
|
@@ -75,8 +75,70 @@ async function cloudflareCommand(options) {
|
|
|
75
75
|
return ensureCloudflareBinary(options);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
var RelayOutput = class _RelayOutput {
|
|
79
|
+
constructor(file, handle) {
|
|
80
|
+
this.file = file;
|
|
81
|
+
this.handle = handle;
|
|
82
|
+
}
|
|
83
|
+
file;
|
|
84
|
+
handle;
|
|
85
|
+
position = 0;
|
|
86
|
+
timer;
|
|
87
|
+
pending = Promise.resolve();
|
|
88
|
+
closing;
|
|
89
|
+
static async create(directory = tmpdir()) {
|
|
90
|
+
const file = path3.join(directory, `.relay-output-${randomUUID()}.log`);
|
|
91
|
+
return new _RelayOutput(file, await open(file, "ax+", 384));
|
|
92
|
+
}
|
|
93
|
+
static async adopt(directory, file) {
|
|
94
|
+
if (path3.dirname(file) !== path3.resolve(directory) || !/^\.relay-output-[a-f0-9-]{36}\.log$/.test(path3.basename(file))) return;
|
|
95
|
+
try {
|
|
96
|
+
const output = new _RelayOutput(file, await open(file, "r+"));
|
|
97
|
+
output.discard();
|
|
98
|
+
return output;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error.code !== "ENOENT") throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
get fd() {
|
|
104
|
+
return this.handle.fd;
|
|
105
|
+
}
|
|
106
|
+
async read() {
|
|
107
|
+
const { size } = await this.handle.stat();
|
|
108
|
+
if (size > 2 * 1024 * 1024) throw new Error("The tunnel helper produced too much startup output. Check its configuration.");
|
|
109
|
+
const data = Buffer.alloc(Math.min(65536, Math.max(0, size - this.position)));
|
|
110
|
+
const { bytesRead } = await this.handle.read(data, 0, data.length, this.position);
|
|
111
|
+
this.position += bytesRead;
|
|
112
|
+
return data.subarray(0, bytesRead);
|
|
113
|
+
}
|
|
114
|
+
discard() {
|
|
115
|
+
if (this.timer || this.closing) return;
|
|
116
|
+
const drain = () => {
|
|
117
|
+
this.pending = this.pending.then(() => this.handle.truncate(0)).catch(() => {
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
drain();
|
|
121
|
+
this.timer = setInterval(drain, 2e3);
|
|
122
|
+
this.timer.unref();
|
|
123
|
+
}
|
|
124
|
+
close() {
|
|
125
|
+
return this.closing ??= (async () => {
|
|
126
|
+
clearInterval(this.timer);
|
|
127
|
+
await this.pending;
|
|
128
|
+
await this.handle.close();
|
|
129
|
+
await rm(this.file, { force: true });
|
|
130
|
+
})();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
78
133
|
|
|
79
134
|
// src/computer/relay.ts
|
|
135
|
+
async function adoptRelay(owner, route, directory, outputFile) {
|
|
136
|
+
const output = outputFile ? await RelayOutput.adopt(directory, outputFile) : void 0;
|
|
137
|
+
return { route, owner, outputFile: output?.file, async close() {
|
|
138
|
+
await owner.close();
|
|
139
|
+
await output?.close();
|
|
140
|
+
} };
|
|
141
|
+
}
|
|
80
142
|
function websocketURL(value) {
|
|
81
143
|
const url = new URL(value);
|
|
82
144
|
if (url.protocol === "https:") url.protocol = "wss:";
|
|
@@ -118,84 +180,78 @@ async function startRelay(options, port, runtime = {}) {
|
|
|
118
180
|
}
|
|
119
181
|
}
|
|
120
182
|
runtime.signal?.throwIfAborted();
|
|
121
|
-
const
|
|
122
|
-
|
|
183
|
+
const output = await RelayOutput.create(runtime.outputDirectory);
|
|
184
|
+
const child = spawn(command, args, { env, detached: true, stdio: ["ignore", output.fd, output.fd], windowsHide: true });
|
|
185
|
+
let failure;
|
|
186
|
+
child.on("error", (error) => {
|
|
187
|
+
failure = error;
|
|
188
|
+
});
|
|
189
|
+
let owner;
|
|
123
190
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
let cloudflareConnected = false;
|
|
150
|
-
const receive = (chunk) => {
|
|
151
|
-
const received = buffer + chunk.toString();
|
|
152
|
-
buffer = received.slice(-16384);
|
|
153
|
-
let found;
|
|
154
|
-
if (options.kind === "cloudflare") {
|
|
155
|
-
cloudflareAddress ??= received.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/)?.[0];
|
|
156
|
-
cloudflareConnected ||= /Registered tunnel connection/.test(received);
|
|
157
|
-
if (cloudflareConnected) found = cloudflareAddress;
|
|
158
|
-
} else {
|
|
159
|
-
for (const line of received.split(/\r?\n/)) {
|
|
160
|
-
try {
|
|
161
|
-
const event = JSON.parse(line);
|
|
162
|
-
if (event.msg === "started tunnel" && event.url?.startsWith("https://")) found = event.url;
|
|
163
|
-
} catch {
|
|
164
|
-
}
|
|
191
|
+
await new Promise((resolve3, reject) => {
|
|
192
|
+
child.once("spawn", resolve3);
|
|
193
|
+
child.once("error", reject);
|
|
194
|
+
});
|
|
195
|
+
owner = await ownChild(child);
|
|
196
|
+
await runtime.onSpawn?.(owner, output.file);
|
|
197
|
+
const deadline = Date.now() + 45e3;
|
|
198
|
+
let buffer = "", cloudflareAddress = options.url, cloudflareConnected = false;
|
|
199
|
+
while (Date.now() < deadline) {
|
|
200
|
+
runtime.signal?.throwIfAborted();
|
|
201
|
+
if (failure) throw new Error(`Could not run ${options.kind}. Check its installation.`, { cause: failure });
|
|
202
|
+
if (!owner.alive()) throw new Error(`${options.kind} exited before the tunnel connected. Check its account configuration.`);
|
|
203
|
+
const received = buffer + (await output.read()).toString();
|
|
204
|
+
buffer = received.slice(-16384);
|
|
205
|
+
let found;
|
|
206
|
+
if (options.kind === "cloudflare") {
|
|
207
|
+
cloudflareAddress ??= received.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/)?.[0];
|
|
208
|
+
cloudflareConnected ||= /Registered tunnel connection/.test(received);
|
|
209
|
+
if (cloudflareConnected) found = cloudflareAddress;
|
|
210
|
+
} else {
|
|
211
|
+
for (const line of received.split(/\r?\n/)) {
|
|
212
|
+
try {
|
|
213
|
+
const event = JSON.parse(line);
|
|
214
|
+
if (event.msg === "started tunnel" && event.url?.startsWith("https://")) found = event.url;
|
|
215
|
+
} catch {
|
|
165
216
|
}
|
|
166
217
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
child.stderr.removeAllListeners("data");
|
|
187
|
-
child.stdout.resume();
|
|
188
|
-
child.stderr.resume();
|
|
189
|
-
child.on("error", () => {
|
|
190
|
-
});
|
|
191
|
-
return { route: { kind: "websocket", url }, process: child, owner, close: () => owner.close() };
|
|
218
|
+
}
|
|
219
|
+
if (found) {
|
|
220
|
+
const route = { kind: "websocket", url: websocketURL(found) };
|
|
221
|
+
output.discard();
|
|
222
|
+
const provider = owner;
|
|
223
|
+
return {
|
|
224
|
+
route,
|
|
225
|
+
process: child,
|
|
226
|
+
owner,
|
|
227
|
+
outputFile: output.file,
|
|
228
|
+
async close() {
|
|
229
|
+
await provider.close();
|
|
230
|
+
await output.close();
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
await setTimeout$1(50, void 0, { signal: runtime.signal });
|
|
235
|
+
}
|
|
236
|
+
throw new Error(`${options.kind} did not establish its tunnel. Check your internet connection and its configuration.`);
|
|
192
237
|
} catch (error) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
238
|
+
if (owner) await owner.close();
|
|
239
|
+
else if (child.pid) await (await ownChild(child)).close();
|
|
240
|
+
await output.close();
|
|
196
241
|
throw error;
|
|
197
242
|
}
|
|
198
243
|
}
|
|
244
|
+
var RelayCheckError = class extends Error {
|
|
245
|
+
constructor(code, message, cause) {
|
|
246
|
+
super(message, cause ? { cause } : void 0);
|
|
247
|
+
this.code = code;
|
|
248
|
+
this.name = "RelayCheckError";
|
|
249
|
+
}
|
|
250
|
+
code;
|
|
251
|
+
};
|
|
252
|
+
function relayNeedsRestart(error, failures, failedForMs) {
|
|
253
|
+
return failures >= 2 && failedForMs >= 9e4 && !(error instanceof RelayCheckError && error.code === "dns");
|
|
254
|
+
}
|
|
199
255
|
var relayLookup = (hostname, options, callback) => {
|
|
200
256
|
lookup(hostname, options, (error, address, family) => {
|
|
201
257
|
if (error?.code !== "ENOTFOUND" || !/^[a-z0-9-]+\.trycloudflare\.com$/i.test(hostname)) {
|
|
@@ -220,8 +276,8 @@ var relayLookup = (hostname, options, callback) => {
|
|
|
220
276
|
async function checkRelay(route, options = {}) {
|
|
221
277
|
options.signal?.throwIfAborted();
|
|
222
278
|
if (route.kind !== "websocket") throw new Error("Expected an internet tunnel address.");
|
|
223
|
-
await new Promise((
|
|
224
|
-
const socket = new WebSocket(route.url, { maxPayload: 4096, perMessageDeflate: false, followRedirects: false, lookup: relayLookup });
|
|
279
|
+
await new Promise((resolve3, reject) => {
|
|
280
|
+
const socket = new WebSocket(route.url, { maxPayload: 4096, perMessageDeflate: false, followRedirects: false, lookup: options.lookup ?? relayLookup });
|
|
225
281
|
let finished = false, banner = "";
|
|
226
282
|
const finish = (error) => {
|
|
227
283
|
if (finished) return;
|
|
@@ -230,27 +286,33 @@ async function checkRelay(route, options = {}) {
|
|
|
230
286
|
options.signal?.removeEventListener("abort", aborted);
|
|
231
287
|
socket.terminate();
|
|
232
288
|
if (error) reject(error);
|
|
233
|
-
else
|
|
289
|
+
else resolve3();
|
|
234
290
|
};
|
|
235
291
|
const aborted = () => finish(options.signal?.reason ?? new Error("Connection check cancelled."));
|
|
236
|
-
const timer = setTimeout(() => finish(new
|
|
237
|
-
socket.on("error", (error) =>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
292
|
+
const timer = setTimeout(() => finish(new RelayCheckError("timeout", "The internet tunnel isn't responding. Retrying\u2026")), options.timeoutMs ?? 8e3);
|
|
293
|
+
socket.on("error", (error) => {
|
|
294
|
+
if (["ENOTFOUND", "EAI_AGAIN"].includes(error.code ?? "")) {
|
|
295
|
+
const provider = new URL(route.url).hostname.endsWith(".trycloudflare.com") ? "Cloudflare's" : "the tunnel's";
|
|
296
|
+
finish(new RelayCheckError("dns", `Waiting for ${provider} internet address (DNS)\u2026`, error));
|
|
297
|
+
} else if (/(?:[Uu]nexpected server response|[Ee]xpected 101)/.test(error.message)) {
|
|
298
|
+
finish(new RelayCheckError("proxy", "The internet tunnel isn't reaching Mindwire. Retrying\u2026", error));
|
|
299
|
+
} else {
|
|
300
|
+
finish(new RelayCheckError("connection", "The internet tunnel couldn't connect. Retrying\u2026", error));
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
socket.on("close", () => finish(new RelayCheckError("connection", "The internet tunnel closed before reaching Mindwire. Retrying\u2026")));
|
|
242
304
|
socket.on("message", (data, binary) => {
|
|
243
305
|
if (!binary) {
|
|
244
|
-
finish(new
|
|
306
|
+
finish(new RelayCheckError("response", "The internet tunnel returned an invalid SSH response."));
|
|
245
307
|
return;
|
|
246
308
|
}
|
|
247
309
|
banner += data.toString();
|
|
248
310
|
if (banner.length > 1024) {
|
|
249
|
-
finish(new
|
|
311
|
+
finish(new RelayCheckError("response", "The internet tunnel returned an invalid SSH response."));
|
|
250
312
|
return;
|
|
251
313
|
}
|
|
252
314
|
if (banner.includes("\n")) {
|
|
253
|
-
finish(banner.split("\n")[0]?.trim() === "SSH-2.0-Mindwire" ? void 0 : new
|
|
315
|
+
finish(banner.split("\n")[0]?.trim() === "SSH-2.0-Mindwire" ? void 0 : new RelayCheckError("response", "The internet tunnel isn't connected to the Mindwire SSH service."));
|
|
254
316
|
}
|
|
255
317
|
});
|
|
256
318
|
options.signal?.addEventListener("abort", aborted, { once: true });
|
|
@@ -260,7 +322,7 @@ async function checkRelay(route, options = {}) {
|
|
|
260
322
|
async function relayProviderReachable(options, signal) {
|
|
261
323
|
if (options.kind !== "cloudflare" && options.kind !== "ngrok") return false;
|
|
262
324
|
signal?.throwIfAborted();
|
|
263
|
-
return new Promise((
|
|
325
|
+
return new Promise((resolve3) => {
|
|
264
326
|
const url = options.kind === "cloudflare" ? "https://api.trycloudflare.com/" : "https://api.ngrok.com/";
|
|
265
327
|
let finished = false;
|
|
266
328
|
const done = (reachable) => {
|
|
@@ -268,7 +330,7 @@ async function relayProviderReachable(options, signal) {
|
|
|
268
330
|
finished = true;
|
|
269
331
|
clearTimeout(timer);
|
|
270
332
|
probe.destroy();
|
|
271
|
-
|
|
333
|
+
resolve3(reachable);
|
|
272
334
|
};
|
|
273
335
|
const probe = request(url, { method: "HEAD", signal }, (response) => {
|
|
274
336
|
response.destroy();
|
|
@@ -285,11 +347,11 @@ function recoveryDelay(failures) {
|
|
|
285
347
|
return Math.min(6e4, 1e3 * 2 ** Math.min(6, Math.max(0, failures - 1)));
|
|
286
348
|
}
|
|
287
349
|
async function superviseComputer(directory) {
|
|
288
|
-
if ((await readJSON(
|
|
289
|
-
let config = await readJSON(
|
|
350
|
+
if ((await readJSON(path3.join(directory, "computer-stopped.json")))?.stopped) return;
|
|
351
|
+
let config = await readJSON(path3.join(directory, "computer-config.json"));
|
|
290
352
|
if (!config) throw new Error("Computer configuration is missing.");
|
|
291
|
-
const controllerPath =
|
|
292
|
-
const relayPath =
|
|
353
|
+
const controllerPath = path3.join(directory, "computer-controller.json");
|
|
354
|
+
const relayPath = path3.join(directory, "computer-relay.json");
|
|
293
355
|
const previous = await readJSON(controllerPath);
|
|
294
356
|
if (await processStateAlive(previous)) throw new Error("The computer already has a service controller.");
|
|
295
357
|
const owner = await currentProcessIdentity();
|
|
@@ -309,30 +371,30 @@ async function superviseComputer(directory) {
|
|
|
309
371
|
process.on("SIGINT", stop);
|
|
310
372
|
let token;
|
|
311
373
|
try {
|
|
312
|
-
token = (await fs.readFile(
|
|
374
|
+
token = (await fs.readFile(path3.join(directory, "daemon.token"), "utf8")).trim();
|
|
313
375
|
} catch (error) {
|
|
314
376
|
if (error.code !== "ENOENT") throw error;
|
|
315
377
|
token = randomBytes(32).toString("hex");
|
|
316
|
-
await fs.writeFile(
|
|
378
|
+
await fs.writeFile(path3.join(directory, "daemon.token"), token, { mode: 384, flag: "wx" });
|
|
317
379
|
}
|
|
318
380
|
const daemonEnv = { ...process.env };
|
|
319
381
|
if (process.platform === "win32") {
|
|
320
382
|
try {
|
|
321
383
|
const git = execFileSync("where.exe", ["git"], { encoding: "utf8", timeout: 3e3, windowsHide: true }).split(/\r?\n/)[0];
|
|
322
384
|
if (git) {
|
|
323
|
-
const shellDirectory =
|
|
324
|
-
await fs.access(
|
|
385
|
+
const shellDirectory = path3.resolve(path3.dirname(git), "..", "bin");
|
|
386
|
+
await fs.access(path3.join(shellDirectory, "bash.exe"));
|
|
325
387
|
const pathKey = Object.keys(daemonEnv).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
326
|
-
daemonEnv[pathKey] = `${shellDirectory}${
|
|
388
|
+
daemonEnv[pathKey] = `${shellDirectory}${path3.delimiter}${daemonEnv[pathKey] ?? ""}`;
|
|
327
389
|
}
|
|
328
390
|
} catch {
|
|
329
391
|
}
|
|
330
392
|
}
|
|
331
|
-
const oldRuntime = await readJSON(
|
|
393
|
+
const oldRuntime = await readJSON(path3.join(directory, "computer-runtime.json"));
|
|
332
394
|
let sshPort = config.sshPort || oldRuntime?.sshPort || 0;
|
|
333
395
|
let websocketPort = config.websocketPort || oldRuntime?.websocketPort || 0;
|
|
334
396
|
const launch = async (binary) => {
|
|
335
|
-
await fs.rm(
|
|
397
|
+
await fs.rm(path3.join(directory, "computer-runtime.json"), { force: true });
|
|
336
398
|
const process2 = spawn(binary, ["--computer"], {
|
|
337
399
|
cwd: config.directory,
|
|
338
400
|
stdio: ["ignore", "inherit", "inherit"],
|
|
@@ -341,8 +403,8 @@ async function superviseComputer(directory) {
|
|
|
341
403
|
...daemonEnv,
|
|
342
404
|
DAEMON_TOKEN: token,
|
|
343
405
|
ADDR: "127.0.0.1:0",
|
|
344
|
-
STATE_PATH:
|
|
345
|
-
WORKSPACE_DB_PATH:
|
|
406
|
+
STATE_PATH: path3.join(directory, "agent-state.json"),
|
|
407
|
+
WORKSPACE_DB_PATH: path3.join(directory, "workspace.db"),
|
|
346
408
|
AGENT_CWD: config.directory,
|
|
347
409
|
COMPUTER_SSH_ADDR: `${config.bind.includes(":") ? `[${config.bind}]` : config.bind}:${sshPort}`,
|
|
348
410
|
COMPUTER_WS_ADDR: `127.0.0.1:${websocketPort}`,
|
|
@@ -353,8 +415,8 @@ async function superviseComputer(directory) {
|
|
|
353
415
|
process2.on("error", (error) => {
|
|
354
416
|
failure = error;
|
|
355
417
|
});
|
|
356
|
-
await new Promise((
|
|
357
|
-
process2.once("spawn",
|
|
418
|
+
await new Promise((resolve3, reject) => {
|
|
419
|
+
process2.once("spawn", resolve3);
|
|
358
420
|
process2.once("error", reject);
|
|
359
421
|
});
|
|
360
422
|
child = await ownChild(process2);
|
|
@@ -381,6 +443,7 @@ async function superviseComputer(directory) {
|
|
|
381
443
|
let routes = [], published = "";
|
|
382
444
|
let client;
|
|
383
445
|
let relayMessage = "Preparing your internet connection\u2026", relayError;
|
|
446
|
+
let relayErrorCode;
|
|
384
447
|
let daemonError, nextDaemonAt = 0, daemonFailures = 0;
|
|
385
448
|
let nextRelayAt = 0, relayFailures = 0, changingDaemon = false;
|
|
386
449
|
let daemonReadyAt = Date.now(), relayReadyAt = Date.now();
|
|
@@ -409,11 +472,16 @@ async function superviseComputer(directory) {
|
|
|
409
472
|
if (saved) {
|
|
410
473
|
const owner2 = await adoptProcess(saved.owner);
|
|
411
474
|
if (owner2) {
|
|
412
|
-
if (saved.route && saved.port === websocketPort && saved.options === JSON.stringify(config.relay)) {
|
|
413
|
-
relay =
|
|
475
|
+
if (saved.outputFile && saved.route && saved.port === websocketPort && saved.options === JSON.stringify(config.relay)) {
|
|
476
|
+
relay = await adoptRelay(owner2, saved.route, directory, saved.outputFile);
|
|
414
477
|
} else {
|
|
415
|
-
await
|
|
478
|
+
if (!saved.outputFile) await phase("Refreshing the internet tunnel for this update\u2026");
|
|
479
|
+
const abandoned = await adoptRelay(owner2, saved.route, directory, saved.outputFile);
|
|
480
|
+
await abandoned.close();
|
|
481
|
+
await fs.rm(relayPath, { force: true });
|
|
416
482
|
}
|
|
483
|
+
} else if (saved.outputFile) {
|
|
484
|
+
await (await RelayOutput.adopt(directory, saved.outputFile))?.close();
|
|
417
485
|
}
|
|
418
486
|
}
|
|
419
487
|
let lastController = "";
|
|
@@ -443,9 +511,11 @@ async function superviseComputer(directory) {
|
|
|
443
511
|
}
|
|
444
512
|
}
|
|
445
513
|
if (relay?.owner && !relay.owner.alive()) {
|
|
514
|
+
await relay.close();
|
|
446
515
|
relay = void 0;
|
|
447
516
|
relayReachable = false;
|
|
448
517
|
relayWasReachable = false;
|
|
518
|
+
relayErrorCode = void 0;
|
|
449
519
|
relayError = "The internet tunnel disconnected. Reconnecting\u2026";
|
|
450
520
|
nextRelayAt = Date.now() + recoveryDelay(++relayFailures);
|
|
451
521
|
}
|
|
@@ -456,6 +526,7 @@ async function superviseComputer(directory) {
|
|
|
456
526
|
if (result.handle) {
|
|
457
527
|
relay = result.handle;
|
|
458
528
|
relayError = void 0;
|
|
529
|
+
relayErrorCode = void 0;
|
|
459
530
|
relayReadyAt = Date.now();
|
|
460
531
|
relayReachable = false;
|
|
461
532
|
relayWasReachable = false;
|
|
@@ -466,7 +537,8 @@ async function superviseComputer(directory) {
|
|
|
466
537
|
owner: relay.owner.identity,
|
|
467
538
|
port: websocketPort,
|
|
468
539
|
options: JSON.stringify(config.relay),
|
|
469
|
-
route: relay.route
|
|
540
|
+
route: relay.route,
|
|
541
|
+
outputFile: relay.outputFile
|
|
470
542
|
});
|
|
471
543
|
} else {
|
|
472
544
|
relayError = result.error instanceof Error ? result.error.message : "The internet tunnel couldn't connect.";
|
|
@@ -474,18 +546,22 @@ async function superviseComputer(directory) {
|
|
|
474
546
|
}
|
|
475
547
|
}
|
|
476
548
|
if (!relay && !relayTask && Date.now() >= nextRelayAt) {
|
|
549
|
+
relayError = void 0;
|
|
550
|
+
relayErrorCode = void 0;
|
|
477
551
|
relayMessage = "Reconnecting your internet tunnel\u2026";
|
|
478
552
|
relayTask = startRelay(config.relay, websocketPort, {
|
|
479
|
-
cacheDir:
|
|
553
|
+
cacheDir: path3.join(directory, "tools"),
|
|
554
|
+
outputDirectory: directory,
|
|
480
555
|
signal: abort.signal,
|
|
481
556
|
onProgress: (message) => {
|
|
482
557
|
relayMessage = message;
|
|
483
558
|
},
|
|
484
|
-
onSpawn: async (owner2) => {
|
|
559
|
+
onSpawn: async (owner2, outputFile) => {
|
|
485
560
|
if (owner2.identity) await writeJSON(relayPath, {
|
|
486
561
|
owner: owner2.identity,
|
|
487
562
|
port: websocketPort,
|
|
488
|
-
options: JSON.stringify(config.relay)
|
|
563
|
+
options: JSON.stringify(config.relay),
|
|
564
|
+
outputFile
|
|
489
565
|
});
|
|
490
566
|
}
|
|
491
567
|
}).then((handle) => {
|
|
@@ -506,9 +582,11 @@ async function superviseComputer(directory) {
|
|
|
506
582
|
relayHealthFailures = 0;
|
|
507
583
|
relayFailedAt = 0;
|
|
508
584
|
relayError = void 0;
|
|
585
|
+
relayErrorCode = void 0;
|
|
509
586
|
nextCheckAt = checkedAt + 3e4;
|
|
510
587
|
} else {
|
|
511
588
|
relayError = result.error instanceof Error ? result.error.message : "The internet tunnel couldn't connect. Retrying\u2026";
|
|
589
|
+
relayErrorCode = result.error instanceof RelayCheckError ? result.error.code : void 0;
|
|
512
590
|
relayFailedAt ||= checkedAt;
|
|
513
591
|
relayHealthFailures++;
|
|
514
592
|
nextCheckAt = checkedAt + Math.min(15e3, recoveryDelay(relayHealthFailures));
|
|
@@ -522,7 +600,7 @@ async function superviseComputer(directory) {
|
|
|
522
600
|
}
|
|
523
601
|
}
|
|
524
602
|
}
|
|
525
|
-
const requestedCheck = await readJSON(
|
|
603
|
+
const requestedCheck = await readJSON(path3.join(directory, "computer-check.json"));
|
|
526
604
|
if (config.relay.kind === "none") checkedAt = Math.max(checkedAt, requestedCheck?.requestedAt ?? 0);
|
|
527
605
|
if (relay?.route && !checkTask && (Date.now() >= nextCheckAt || (requestedCheck?.requestedAt ?? 0) > checkedAt)) {
|
|
528
606
|
const checking = relay;
|
|
@@ -531,7 +609,7 @@ async function superviseComputer(directory) {
|
|
|
531
609
|
await checkRelay(checking.route, { signal: abort.signal });
|
|
532
610
|
checkResult = { relay: checking };
|
|
533
611
|
} catch (error) {
|
|
534
|
-
const canRestart = !!checking.owner &&
|
|
612
|
+
const canRestart = !!checking.owner && relayFailedAt > 0 && relayNeedsRestart(error, relayHealthFailures, Date.now() - relayFailedAt) && await relayProviderReachable(config.relay, abort.signal).catch(() => false);
|
|
535
613
|
checkResult = { relay: checking, error, canRestart };
|
|
536
614
|
}
|
|
537
615
|
})();
|
|
@@ -556,6 +634,7 @@ async function superviseComputer(directory) {
|
|
|
556
634
|
checkedAt,
|
|
557
635
|
phase: ready ? void 0 : daemonError ?? relayError ?? relayMessage,
|
|
558
636
|
error: daemonError ?? relayError,
|
|
637
|
+
errorCode: daemonError ? void 0 : relayErrorCode,
|
|
559
638
|
recovering: !ready
|
|
560
639
|
};
|
|
561
640
|
const serialized = JSON.stringify(state);
|
|
@@ -563,7 +642,7 @@ async function superviseComputer(directory) {
|
|
|
563
642
|
await writeJSON(controllerPath, state);
|
|
564
643
|
lastController = serialized;
|
|
565
644
|
}
|
|
566
|
-
const updatePath =
|
|
645
|
+
const updatePath = path3.join(directory, "computer-update.json");
|
|
567
646
|
const update = !updateTask && client && child?.alive() ? await readJSON(updatePath) : void 0;
|
|
568
647
|
if (update && ["queued", "downloading", "waiting", "restarting"].includes(update.status)) {
|
|
569
648
|
updateTask = (async () => {
|
|
@@ -603,7 +682,7 @@ async function superviseComputer(directory) {
|
|
|
603
682
|
}
|
|
604
683
|
binary = replacement;
|
|
605
684
|
config = { ...config, daemonBin: void 0, version: update.version };
|
|
606
|
-
await writeJSON(
|
|
685
|
+
await writeJSON(path3.join(directory, "computer-config.json"), config);
|
|
607
686
|
await client.computer.setRoutes(routes);
|
|
608
687
|
published = JSON.stringify(routes);
|
|
609
688
|
await status("complete");
|
|
@@ -637,5 +716,5 @@ async function superviseComputer(directory) {
|
|
|
637
716
|
}
|
|
638
717
|
|
|
639
718
|
export { recoveryDelay, superviseComputer };
|
|
640
|
-
//# sourceMappingURL=controller-
|
|
641
|
-
//# sourceMappingURL=controller-
|
|
719
|
+
//# sourceMappingURL=controller-QBC6L7AT.js.map
|
|
720
|
+
//# sourceMappingURL=controller-QBC6L7AT.js.map
|