@camstack/addon-cloudflare 1.0.3 → 1.0.5
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/{dist-FQAxmoZY.js → dist-Bj5XzuE6.js} +315 -30
- package/dist/{dist-gS41k7Cx.mjs → dist-D4obfhYq.mjs} +315 -30
- package/dist/tunnel/cloudflare-tunnel.addon.js +189 -5
- package/dist/tunnel/cloudflare-tunnel.addon.mjs +163 -5
- package/dist/turn/cloudflare-turn.addon.js +1 -1
- package/dist/turn/cloudflare-turn.addon.mjs +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,38 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
const require_dist = require("../dist-Bj5XzuE6.js");
|
|
25
|
+
let node_path = require("node:path");
|
|
26
|
+
node_path = __toESM(node_path);
|
|
3
27
|
let node_crypto = require("node:crypto");
|
|
4
28
|
let node_child_process = require("node:child_process");
|
|
29
|
+
let node_fs = require("node:fs");
|
|
30
|
+
node_fs = __toESM(node_fs);
|
|
31
|
+
let node_fs_promises = require("node:fs/promises");
|
|
32
|
+
node_fs_promises = __toESM(node_fs_promises);
|
|
33
|
+
let node_os = require("node:os");
|
|
34
|
+
node_os = __toESM(node_os);
|
|
35
|
+
let node_stream_promises = require("node:stream/promises");
|
|
5
36
|
//#region src/tunnel/cloudflare-tunnel.ts
|
|
6
37
|
/**
|
|
7
38
|
* Direct child_process.spawn() driver for the `cloudflared` binary.
|
|
@@ -23,19 +54,23 @@ var CloudflareTunnelService = class CloudflareTunnelService {
|
|
|
23
54
|
config;
|
|
24
55
|
logger;
|
|
25
56
|
eventBus;
|
|
57
|
+
resolveCloudflaredBin;
|
|
26
58
|
id = "cloudflare-tunnel";
|
|
27
59
|
type = "cloudflare";
|
|
28
60
|
endpoint = null;
|
|
29
61
|
lastError;
|
|
30
62
|
child = null;
|
|
63
|
+
/** Resolved cloudflared path/command (set on first start, reused on restart). */
|
|
64
|
+
cloudflaredBin = "cloudflared";
|
|
31
65
|
restartCount = 0;
|
|
32
66
|
intentionalStop = false;
|
|
33
67
|
static MAX_RESTARTS = 5;
|
|
34
68
|
static STOP_GRACE_MS = 5e3;
|
|
35
|
-
constructor(config, logger, eventBus) {
|
|
69
|
+
constructor(config, logger, eventBus, resolveCloudflaredBin = async () => "cloudflared") {
|
|
36
70
|
this.config = config;
|
|
37
71
|
this.logger = logger;
|
|
38
72
|
this.eventBus = eventBus;
|
|
73
|
+
this.resolveCloudflaredBin = resolveCloudflaredBin;
|
|
39
74
|
}
|
|
40
75
|
async start() {
|
|
41
76
|
this.logger.info("Starting Cloudflare tunnel", {
|
|
@@ -76,6 +111,18 @@ var CloudflareTunnelService = class CloudflareTunnelService {
|
|
|
76
111
|
this.intentionalStop = false;
|
|
77
112
|
this.restartCount = 0;
|
|
78
113
|
this.lastError = void 0;
|
|
114
|
+
try {
|
|
115
|
+
this.cloudflaredBin = await this.resolveCloudflaredBin();
|
|
116
|
+
} catch (err) {
|
|
117
|
+
this.logger.error("Failed to provision cloudflared binary", {
|
|
118
|
+
meta: { error: err instanceof Error ? err.message : String(err) },
|
|
119
|
+
tags: {
|
|
120
|
+
topic: "tunnel",
|
|
121
|
+
phase: "provision-error"
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
79
126
|
this.spawnChild();
|
|
80
127
|
const placeholderHost = this.config.mode === "custom" ? this.config.customHostname : "pending.trycloudflare.com";
|
|
81
128
|
this.endpoint = {
|
|
@@ -192,7 +239,7 @@ var CloudflareTunnelService = class CloudflareTunnelService {
|
|
|
192
239
|
const args = [...debugFlags, ...tunnelArgs];
|
|
193
240
|
let child;
|
|
194
241
|
try {
|
|
195
|
-
child = (0, node_child_process.spawn)(
|
|
242
|
+
child = (0, node_child_process.spawn)(this.cloudflaredBin, args, { stdio: [
|
|
196
243
|
"ignore",
|
|
197
244
|
"pipe",
|
|
198
245
|
"pipe"
|
|
@@ -340,6 +387,130 @@ var CloudflareTunnelService = class CloudflareTunnelService {
|
|
|
340
387
|
});
|
|
341
388
|
}
|
|
342
389
|
};
|
|
390
|
+
var RELEASE_BASE = "https://github.com/cloudflare/cloudflared/releases/download";
|
|
391
|
+
/**
|
|
392
|
+
* Map a Node `platform`/`arch` to the matching cloudflared release asset.
|
|
393
|
+
* Pure + exported for testing. Throws on an unsupported combination.
|
|
394
|
+
*/
|
|
395
|
+
function cloudflaredAsset(platform, arch) {
|
|
396
|
+
const a = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : arch === "arm" ? "arm" : null;
|
|
397
|
+
if (a === null) throw new Error(`cloudflared: unsupported arch '${arch}'`);
|
|
398
|
+
switch (platform) {
|
|
399
|
+
case "linux": return {
|
|
400
|
+
fileName: `cloudflared-linux-${a}`,
|
|
401
|
+
isArchive: false
|
|
402
|
+
};
|
|
403
|
+
case "darwin": return {
|
|
404
|
+
fileName: `cloudflared-darwin-${a === "arm64" ? "amd64" : a}.tgz`,
|
|
405
|
+
isArchive: true
|
|
406
|
+
};
|
|
407
|
+
case "win32": return {
|
|
408
|
+
fileName: `cloudflared-windows-${a}.exe`,
|
|
409
|
+
isArchive: false
|
|
410
|
+
};
|
|
411
|
+
default: throw new Error(`cloudflared: unsupported platform '${platform}'`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/** Resolve a working `cloudflared` from PATH, or null if not runnable. */
|
|
415
|
+
async function cloudflaredOnPath() {
|
|
416
|
+
return await runsOk("cloudflared") ? "cloudflared" : null;
|
|
417
|
+
}
|
|
418
|
+
/** True when `<bin> --version` exits 0 (binary present + executable). */
|
|
419
|
+
function runsOk(bin) {
|
|
420
|
+
return new Promise((resolve) => {
|
|
421
|
+
let done = false;
|
|
422
|
+
const finish = (ok) => {
|
|
423
|
+
if (!done) {
|
|
424
|
+
done = true;
|
|
425
|
+
resolve(ok);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
try {
|
|
429
|
+
const child = (0, node_child_process.spawn)(bin, ["--version"], { stdio: "ignore" });
|
|
430
|
+
child.on("error", () => finish(false));
|
|
431
|
+
child.on("exit", (code) => finish(code === 0));
|
|
432
|
+
setTimeout(() => {
|
|
433
|
+
child.kill("SIGKILL");
|
|
434
|
+
finish(false);
|
|
435
|
+
}, 5e3);
|
|
436
|
+
} catch {
|
|
437
|
+
finish(false);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async function downloadTo(url, dest) {
|
|
442
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
443
|
+
if (!res.ok || !res.body) throw new Error(`cloudflared download failed: HTTP ${res.status} for ${url}`);
|
|
444
|
+
const tmp = `${dest}.partial`;
|
|
445
|
+
await (0, node_stream_promises.pipeline)(res.body, node_fs.createWriteStream(tmp));
|
|
446
|
+
await node_fs_promises.rename(tmp, dest);
|
|
447
|
+
}
|
|
448
|
+
/** Extract the `cloudflared` entry from a downloaded .tgz into `destBin`. */
|
|
449
|
+
async function extractArchive(archivePath, destBin, logger) {
|
|
450
|
+
const dir = node_path.dirname(destBin);
|
|
451
|
+
await new Promise((resolve, reject) => {
|
|
452
|
+
const child = (0, node_child_process.spawn)("tar", [
|
|
453
|
+
"-xzf",
|
|
454
|
+
archivePath,
|
|
455
|
+
"-C",
|
|
456
|
+
dir
|
|
457
|
+
], { stdio: "inherit" });
|
|
458
|
+
child.on("error", reject);
|
|
459
|
+
child.on("exit", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`tar exited ${code}`)));
|
|
460
|
+
});
|
|
461
|
+
if (!node_fs.existsSync(destBin)) logger.warn("cloudflared archive did not yield expected binary name", {
|
|
462
|
+
meta: { destBin },
|
|
463
|
+
tags: {
|
|
464
|
+
topic: "tunnel",
|
|
465
|
+
phase: "extract"
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
await node_fs_promises.rm(archivePath, { force: true });
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Return a path/command for a runnable `cloudflared`, downloading + caching it
|
|
472
|
+
* on first use. Prefers an existing PATH install; otherwise caches under
|
|
473
|
+
* `binDir`. Throws (with an actionable message) if it cannot be provisioned.
|
|
474
|
+
*/
|
|
475
|
+
async function ensureCloudflared(opts) {
|
|
476
|
+
const onPath = await cloudflaredOnPath();
|
|
477
|
+
if (onPath) return onPath;
|
|
478
|
+
const cached = node_path.join(opts.binDir, node_os.platform() === "win32" ? "cloudflared.exe" : "cloudflared");
|
|
479
|
+
if (node_fs.existsSync(cached) && await runsOk(cached)) return cached;
|
|
480
|
+
const version = opts.version ?? "2025.4.2";
|
|
481
|
+
const asset = cloudflaredAsset(node_os.platform(), node_os.arch());
|
|
482
|
+
const url = `${RELEASE_BASE}/${version}/${asset.fileName}`;
|
|
483
|
+
await node_fs_promises.mkdir(opts.binDir, { recursive: true });
|
|
484
|
+
opts.logger.info("Provisioning cloudflared (not on PATH) — downloading", {
|
|
485
|
+
meta: {
|
|
486
|
+
version,
|
|
487
|
+
asset: asset.fileName,
|
|
488
|
+
dest: cached
|
|
489
|
+
},
|
|
490
|
+
tags: {
|
|
491
|
+
topic: "tunnel",
|
|
492
|
+
phase: "provision"
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
if (asset.isArchive) {
|
|
496
|
+
const archive = node_path.join(opts.binDir, asset.fileName);
|
|
497
|
+
await downloadTo(url, archive);
|
|
498
|
+
await extractArchive(archive, cached, opts.logger);
|
|
499
|
+
} else await downloadTo(url, cached);
|
|
500
|
+
await node_fs_promises.chmod(cached, 493);
|
|
501
|
+
if (!await runsOk(cached)) throw new Error(`cloudflared downloaded to ${cached} but is not runnable`);
|
|
502
|
+
opts.logger.info("cloudflared ready", {
|
|
503
|
+
meta: {
|
|
504
|
+
path: cached,
|
|
505
|
+
version
|
|
506
|
+
},
|
|
507
|
+
tags: {
|
|
508
|
+
topic: "tunnel",
|
|
509
|
+
phase: "provision-done"
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
return cached;
|
|
513
|
+
}
|
|
343
514
|
//#endregion
|
|
344
515
|
//#region src/tunnel/cloudflare-api.ts
|
|
345
516
|
/**
|
|
@@ -666,6 +837,19 @@ var CloudflareTunnelAddon = class extends require_dist.BaseAddon {
|
|
|
666
837
|
}
|
|
667
838
|
return "127.0.0.1";
|
|
668
839
|
}
|
|
840
|
+
/**
|
|
841
|
+
* Resolve the `cloudflared` binary path once per addon lifetime (PATH install,
|
|
842
|
+
* or a copy downloaded + cached under `<dataDir>/bin`). Memoized so the
|
|
843
|
+
* (possible) download happens at most once, on the first tunnel start.
|
|
844
|
+
*/
|
|
845
|
+
cloudflaredBinPromise;
|
|
846
|
+
resolveCloudflared() {
|
|
847
|
+
this.cloudflaredBinPromise ??= ensureCloudflared({
|
|
848
|
+
binDir: node_path.join(this.ctx.dataDir, "bin"),
|
|
849
|
+
logger: this.ctx.logger
|
|
850
|
+
});
|
|
851
|
+
return this.cloudflaredBinPromise;
|
|
852
|
+
}
|
|
669
853
|
async onInitialize() {
|
|
670
854
|
if (this.config.localPort === 3001) {
|
|
671
855
|
this.ctx.logger.warn("Resetting stale localPort=3001 (Vite admin-ui dev port) → 0 (auto)", { tags: {
|
|
@@ -679,7 +863,7 @@ var CloudflareTunnelAddon = class extends require_dist.BaseAddon {
|
|
|
679
863
|
...this.config,
|
|
680
864
|
localPort: this.resolveLocalPort(),
|
|
681
865
|
localHost
|
|
682
|
-
}, this.ctx.logger, this.ctx.eventBus);
|
|
866
|
+
}, this.ctx.logger, this.ctx.eventBus, () => this.resolveCloudflared());
|
|
683
867
|
this.ctx.logger.info("Cloudflare Tunnel addon initialized", { meta: {
|
|
684
868
|
mode: this.config.mode,
|
|
685
869
|
hasCustomToken: !!this.config.customTunnelToken
|
|
@@ -735,7 +919,7 @@ var CloudflareTunnelAddon = class extends require_dist.BaseAddon {
|
|
|
735
919
|
...this.config,
|
|
736
920
|
localPort: this.resolveLocalPort(),
|
|
737
921
|
localHost
|
|
738
|
-
}, this.ctx.logger, this.ctx.eventBus);
|
|
922
|
+
}, this.ctx.logger, this.ctx.eventBus, () => this.resolveCloudflared());
|
|
739
923
|
if (this.isRunCapable()) {
|
|
740
924
|
this.ctx.logger.info("config changed — (re-)spawning tunnel", {
|
|
741
925
|
meta: {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { a as networkAccessCapability, c as array, d as literal, f as number, i as defineCustomActions, l as boolean, m as string, n as EventCategory, p as object, r as customAction, s as _enum, t as BaseAddon } from "../dist-
|
|
1
|
+
import { a as networkAccessCapability, c as array, d as literal, f as number, i as defineCustomActions, l as boolean, m as string, n as EventCategory, p as object, r as customAction, s as _enum, t as BaseAddon } from "../dist-D4obfhYq.mjs";
|
|
2
|
+
import * as path from "node:path";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { spawn } from "node:child_process";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as fsp from "node:fs/promises";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import { pipeline } from "node:stream/promises";
|
|
4
9
|
//#region src/tunnel/cloudflare-tunnel.ts
|
|
5
10
|
/**
|
|
6
11
|
* Direct child_process.spawn() driver for the `cloudflared` binary.
|
|
@@ -22,19 +27,23 @@ var CloudflareTunnelService = class CloudflareTunnelService {
|
|
|
22
27
|
config;
|
|
23
28
|
logger;
|
|
24
29
|
eventBus;
|
|
30
|
+
resolveCloudflaredBin;
|
|
25
31
|
id = "cloudflare-tunnel";
|
|
26
32
|
type = "cloudflare";
|
|
27
33
|
endpoint = null;
|
|
28
34
|
lastError;
|
|
29
35
|
child = null;
|
|
36
|
+
/** Resolved cloudflared path/command (set on first start, reused on restart). */
|
|
37
|
+
cloudflaredBin = "cloudflared";
|
|
30
38
|
restartCount = 0;
|
|
31
39
|
intentionalStop = false;
|
|
32
40
|
static MAX_RESTARTS = 5;
|
|
33
41
|
static STOP_GRACE_MS = 5e3;
|
|
34
|
-
constructor(config, logger, eventBus) {
|
|
42
|
+
constructor(config, logger, eventBus, resolveCloudflaredBin = async () => "cloudflared") {
|
|
35
43
|
this.config = config;
|
|
36
44
|
this.logger = logger;
|
|
37
45
|
this.eventBus = eventBus;
|
|
46
|
+
this.resolveCloudflaredBin = resolveCloudflaredBin;
|
|
38
47
|
}
|
|
39
48
|
async start() {
|
|
40
49
|
this.logger.info("Starting Cloudflare tunnel", {
|
|
@@ -75,6 +84,18 @@ var CloudflareTunnelService = class CloudflareTunnelService {
|
|
|
75
84
|
this.intentionalStop = false;
|
|
76
85
|
this.restartCount = 0;
|
|
77
86
|
this.lastError = void 0;
|
|
87
|
+
try {
|
|
88
|
+
this.cloudflaredBin = await this.resolveCloudflaredBin();
|
|
89
|
+
} catch (err) {
|
|
90
|
+
this.logger.error("Failed to provision cloudflared binary", {
|
|
91
|
+
meta: { error: err instanceof Error ? err.message : String(err) },
|
|
92
|
+
tags: {
|
|
93
|
+
topic: "tunnel",
|
|
94
|
+
phase: "provision-error"
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
78
99
|
this.spawnChild();
|
|
79
100
|
const placeholderHost = this.config.mode === "custom" ? this.config.customHostname : "pending.trycloudflare.com";
|
|
80
101
|
this.endpoint = {
|
|
@@ -191,7 +212,7 @@ var CloudflareTunnelService = class CloudflareTunnelService {
|
|
|
191
212
|
const args = [...debugFlags, ...tunnelArgs];
|
|
192
213
|
let child;
|
|
193
214
|
try {
|
|
194
|
-
child = spawn(
|
|
215
|
+
child = spawn(this.cloudflaredBin, args, { stdio: [
|
|
195
216
|
"ignore",
|
|
196
217
|
"pipe",
|
|
197
218
|
"pipe"
|
|
@@ -339,6 +360,130 @@ var CloudflareTunnelService = class CloudflareTunnelService {
|
|
|
339
360
|
});
|
|
340
361
|
}
|
|
341
362
|
};
|
|
363
|
+
var RELEASE_BASE = "https://github.com/cloudflare/cloudflared/releases/download";
|
|
364
|
+
/**
|
|
365
|
+
* Map a Node `platform`/`arch` to the matching cloudflared release asset.
|
|
366
|
+
* Pure + exported for testing. Throws on an unsupported combination.
|
|
367
|
+
*/
|
|
368
|
+
function cloudflaredAsset(platform, arch) {
|
|
369
|
+
const a = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : arch === "arm" ? "arm" : null;
|
|
370
|
+
if (a === null) throw new Error(`cloudflared: unsupported arch '${arch}'`);
|
|
371
|
+
switch (platform) {
|
|
372
|
+
case "linux": return {
|
|
373
|
+
fileName: `cloudflared-linux-${a}`,
|
|
374
|
+
isArchive: false
|
|
375
|
+
};
|
|
376
|
+
case "darwin": return {
|
|
377
|
+
fileName: `cloudflared-darwin-${a === "arm64" ? "amd64" : a}.tgz`,
|
|
378
|
+
isArchive: true
|
|
379
|
+
};
|
|
380
|
+
case "win32": return {
|
|
381
|
+
fileName: `cloudflared-windows-${a}.exe`,
|
|
382
|
+
isArchive: false
|
|
383
|
+
};
|
|
384
|
+
default: throw new Error(`cloudflared: unsupported platform '${platform}'`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/** Resolve a working `cloudflared` from PATH, or null if not runnable. */
|
|
388
|
+
async function cloudflaredOnPath() {
|
|
389
|
+
return await runsOk("cloudflared") ? "cloudflared" : null;
|
|
390
|
+
}
|
|
391
|
+
/** True when `<bin> --version` exits 0 (binary present + executable). */
|
|
392
|
+
function runsOk(bin) {
|
|
393
|
+
return new Promise((resolve) => {
|
|
394
|
+
let done = false;
|
|
395
|
+
const finish = (ok) => {
|
|
396
|
+
if (!done) {
|
|
397
|
+
done = true;
|
|
398
|
+
resolve(ok);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
try {
|
|
402
|
+
const child = spawn(bin, ["--version"], { stdio: "ignore" });
|
|
403
|
+
child.on("error", () => finish(false));
|
|
404
|
+
child.on("exit", (code) => finish(code === 0));
|
|
405
|
+
setTimeout(() => {
|
|
406
|
+
child.kill("SIGKILL");
|
|
407
|
+
finish(false);
|
|
408
|
+
}, 5e3);
|
|
409
|
+
} catch {
|
|
410
|
+
finish(false);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
async function downloadTo(url, dest) {
|
|
415
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
416
|
+
if (!res.ok || !res.body) throw new Error(`cloudflared download failed: HTTP ${res.status} for ${url}`);
|
|
417
|
+
const tmp = `${dest}.partial`;
|
|
418
|
+
await pipeline(res.body, fs.createWriteStream(tmp));
|
|
419
|
+
await fsp.rename(tmp, dest);
|
|
420
|
+
}
|
|
421
|
+
/** Extract the `cloudflared` entry from a downloaded .tgz into `destBin`. */
|
|
422
|
+
async function extractArchive(archivePath, destBin, logger) {
|
|
423
|
+
const dir = path.dirname(destBin);
|
|
424
|
+
await new Promise((resolve, reject) => {
|
|
425
|
+
const child = spawn("tar", [
|
|
426
|
+
"-xzf",
|
|
427
|
+
archivePath,
|
|
428
|
+
"-C",
|
|
429
|
+
dir
|
|
430
|
+
], { stdio: "inherit" });
|
|
431
|
+
child.on("error", reject);
|
|
432
|
+
child.on("exit", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`tar exited ${code}`)));
|
|
433
|
+
});
|
|
434
|
+
if (!fs.existsSync(destBin)) logger.warn("cloudflared archive did not yield expected binary name", {
|
|
435
|
+
meta: { destBin },
|
|
436
|
+
tags: {
|
|
437
|
+
topic: "tunnel",
|
|
438
|
+
phase: "extract"
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
await fsp.rm(archivePath, { force: true });
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Return a path/command for a runnable `cloudflared`, downloading + caching it
|
|
445
|
+
* on first use. Prefers an existing PATH install; otherwise caches under
|
|
446
|
+
* `binDir`. Throws (with an actionable message) if it cannot be provisioned.
|
|
447
|
+
*/
|
|
448
|
+
async function ensureCloudflared(opts) {
|
|
449
|
+
const onPath = await cloudflaredOnPath();
|
|
450
|
+
if (onPath) return onPath;
|
|
451
|
+
const cached = path.join(opts.binDir, os.platform() === "win32" ? "cloudflared.exe" : "cloudflared");
|
|
452
|
+
if (fs.existsSync(cached) && await runsOk(cached)) return cached;
|
|
453
|
+
const version = opts.version ?? "2025.4.2";
|
|
454
|
+
const asset = cloudflaredAsset(os.platform(), os.arch());
|
|
455
|
+
const url = `${RELEASE_BASE}/${version}/${asset.fileName}`;
|
|
456
|
+
await fsp.mkdir(opts.binDir, { recursive: true });
|
|
457
|
+
opts.logger.info("Provisioning cloudflared (not on PATH) — downloading", {
|
|
458
|
+
meta: {
|
|
459
|
+
version,
|
|
460
|
+
asset: asset.fileName,
|
|
461
|
+
dest: cached
|
|
462
|
+
},
|
|
463
|
+
tags: {
|
|
464
|
+
topic: "tunnel",
|
|
465
|
+
phase: "provision"
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
if (asset.isArchive) {
|
|
469
|
+
const archive = path.join(opts.binDir, asset.fileName);
|
|
470
|
+
await downloadTo(url, archive);
|
|
471
|
+
await extractArchive(archive, cached, opts.logger);
|
|
472
|
+
} else await downloadTo(url, cached);
|
|
473
|
+
await fsp.chmod(cached, 493);
|
|
474
|
+
if (!await runsOk(cached)) throw new Error(`cloudflared downloaded to ${cached} but is not runnable`);
|
|
475
|
+
opts.logger.info("cloudflared ready", {
|
|
476
|
+
meta: {
|
|
477
|
+
path: cached,
|
|
478
|
+
version
|
|
479
|
+
},
|
|
480
|
+
tags: {
|
|
481
|
+
topic: "tunnel",
|
|
482
|
+
phase: "provision-done"
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
return cached;
|
|
486
|
+
}
|
|
342
487
|
//#endregion
|
|
343
488
|
//#region src/tunnel/cloudflare-api.ts
|
|
344
489
|
/**
|
|
@@ -665,6 +810,19 @@ var CloudflareTunnelAddon = class extends BaseAddon {
|
|
|
665
810
|
}
|
|
666
811
|
return "127.0.0.1";
|
|
667
812
|
}
|
|
813
|
+
/**
|
|
814
|
+
* Resolve the `cloudflared` binary path once per addon lifetime (PATH install,
|
|
815
|
+
* or a copy downloaded + cached under `<dataDir>/bin`). Memoized so the
|
|
816
|
+
* (possible) download happens at most once, on the first tunnel start.
|
|
817
|
+
*/
|
|
818
|
+
cloudflaredBinPromise;
|
|
819
|
+
resolveCloudflared() {
|
|
820
|
+
this.cloudflaredBinPromise ??= ensureCloudflared({
|
|
821
|
+
binDir: path.join(this.ctx.dataDir, "bin"),
|
|
822
|
+
logger: this.ctx.logger
|
|
823
|
+
});
|
|
824
|
+
return this.cloudflaredBinPromise;
|
|
825
|
+
}
|
|
668
826
|
async onInitialize() {
|
|
669
827
|
if (this.config.localPort === 3001) {
|
|
670
828
|
this.ctx.logger.warn("Resetting stale localPort=3001 (Vite admin-ui dev port) → 0 (auto)", { tags: {
|
|
@@ -678,7 +836,7 @@ var CloudflareTunnelAddon = class extends BaseAddon {
|
|
|
678
836
|
...this.config,
|
|
679
837
|
localPort: this.resolveLocalPort(),
|
|
680
838
|
localHost
|
|
681
|
-
}, this.ctx.logger, this.ctx.eventBus);
|
|
839
|
+
}, this.ctx.logger, this.ctx.eventBus, () => this.resolveCloudflared());
|
|
682
840
|
this.ctx.logger.info("Cloudflare Tunnel addon initialized", { meta: {
|
|
683
841
|
mode: this.config.mode,
|
|
684
842
|
hasCustomToken: !!this.config.customTunnelToken
|
|
@@ -734,7 +892,7 @@ var CloudflareTunnelAddon = class extends BaseAddon {
|
|
|
734
892
|
...this.config,
|
|
735
893
|
localPort: this.resolveLocalPort(),
|
|
736
894
|
localHost
|
|
737
|
-
}, this.ctx.logger, this.ctx.eventBus);
|
|
895
|
+
}, this.ctx.logger, this.ctx.eventBus, () => this.resolveCloudflared());
|
|
738
896
|
if (this.isRunCapable()) {
|
|
739
897
|
this.ctx.logger.info("config changed — (re-)spawning tunnel", {
|
|
740
898
|
meta: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("../dist-
|
|
2
|
+
const require_dist = require("../dist-Bj5XzuE6.js");
|
|
3
3
|
//#region src/turn/cloudflare-turn.ts
|
|
4
4
|
/**
|
|
5
5
|
* Cloudflare returns ICE servers in several flavours depending on which
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as literal, f as number, i as defineCustomActions, l as boolean, m as string, o as turnProviderCapability, p as object, r as customAction, t as BaseAddon, u as discriminatedUnion } from "../dist-
|
|
1
|
+
import { d as literal, f as number, i as defineCustomActions, l as boolean, m as string, o as turnProviderCapability, p as object, r as customAction, t as BaseAddon, u as discriminatedUnion } from "../dist-D4obfhYq.mjs";
|
|
2
2
|
//#region src/turn/cloudflare-turn.ts
|
|
3
3
|
/**
|
|
4
4
|
* Cloudflare returns ICE servers in several flavours depending on which
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-cloudflare",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Cloudflare bundle — Tunnel (network-access) + TURN relay (turn-provider). Multi-entry npm package shipping 2 addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|