@farthershore/backend 0.8.0 → 0.8.2
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 +1 -1
- package/dist/generated/runtime-contract.js +7 -8
- package/dist/index.js +57 -24
- package/dist/reflect/index.js +94 -0
- package/dist/types/core/runtime.d.ts +8 -8
- package/dist/types/core/tunnel.d.ts +10 -2
- package/dist/types/generated/runtime-contract.d.ts +7 -8
- package/dist/types/runtime-types.d.ts +2 -3
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Runtime metering and gateway-verification SDK for builder upstreams. Install one
|
|
|
4
4
|
package, set one token (`FS_RUNTIME_TOKEN`), and Farther Shore handles signed
|
|
5
5
|
gateway-to-upstream request verification plus response-bound usage reporting.
|
|
6
6
|
|
|
7
|
-
> **Status: `0.8.
|
|
7
|
+
> **Status: `0.8.2` (lockstep SDK family).** Published at the SAME version as
|
|
8
8
|
> `@farthershore/farthershore-js` and `@farthershore/product` — pin the three
|
|
9
9
|
> together. Pre-1.0: minor bumps may break.
|
|
10
10
|
|
|
@@ -54,8 +54,8 @@ var RUNTIME_BOOTSTRAP_CONTRACT = {
|
|
|
54
54
|
perEventMax: "number"
|
|
55
55
|
},
|
|
56
56
|
transport: {
|
|
57
|
-
mode: "
|
|
58
|
-
runner: "
|
|
57
|
+
mode: "direct | tunnel",
|
|
58
|
+
runner: "embedded | sidecar | null",
|
|
59
59
|
originUrl: "string?",
|
|
60
60
|
originHostname: "string?",
|
|
61
61
|
localTarget: "string?",
|
|
@@ -247,17 +247,16 @@ var RUNTIME_HEALTH_CONTRACT = {
|
|
|
247
247
|
};
|
|
248
248
|
var RUNTIME_TRANSPORT_CONTRACT = {
|
|
249
249
|
modes: {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
cloudflare_tunnel: "Farther Shore provisions a private outbound Cloudflare Tunnel; no inbound port. The only Production-secure tier. Consumes Cloudflare tunnel/route slots."
|
|
250
|
+
direct: "Gateway fetches the builder's public origin URL; the SDK middleware fail-closed-verifies every request via Ed25519 request signing. Provisions zero Cloudflare objects. Available on all tiers; also the dev path.",
|
|
251
|
+
tunnel: "Farther Shore provisions a private outbound Cloudflare Tunnel; no inbound port. The Production-secure tier. Consumes Cloudflare tunnel/route slots."
|
|
253
252
|
},
|
|
254
253
|
runners: {
|
|
255
|
-
|
|
254
|
+
embedded: "fs.start() supervises cloudflared as a child process (default DX).",
|
|
256
255
|
sidecar: "Vanilla cloudflare/cloudflared container beside the app (production / non-Node)."
|
|
257
256
|
},
|
|
258
|
-
channelTrust: ["
|
|
257
|
+
channelTrust: ["tunnel"],
|
|
259
258
|
requestTrust: "x-fs-signature",
|
|
260
|
-
invariant: "Channel trust (
|
|
259
|
+
invariant: "Channel trust (tunnel) and request trust (the X-FS-* signature) are distinct layers; both always apply. CF-Access-* headers are transport-layer only and are IGNORED by the SDK."
|
|
261
260
|
};
|
|
262
261
|
export {
|
|
263
262
|
RUNTIME_BODY_HASH_CONTRACT,
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ var __export = (target, all) => {
|
|
|
12
12
|
// src/reflect/route-canon.ts
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
14
|
function normalizeSegment(seg) {
|
|
15
|
-
if (seg === "**") return "{
|
|
15
|
+
if (seg === "**") return "{__fs_rest}";
|
|
16
16
|
if (seg === "*") return "{splat}";
|
|
17
17
|
if (seg.startsWith(":")) return `{${seg.slice(1)}}`;
|
|
18
18
|
if (seg.startsWith("[") && seg.endsWith("]")) return `{${seg.slice(1, -1)}}`;
|
|
@@ -62,6 +62,7 @@ function methodsOf(route) {
|
|
|
62
62
|
for (const s of route.stack ?? []) {
|
|
63
63
|
if (typeof s.method === "string") out.add(s.method.toUpperCase());
|
|
64
64
|
}
|
|
65
|
+
if (out.has("GET")) out.delete("HEAD");
|
|
65
66
|
return [...out].filter((m) => VALID_METHODS.has(m));
|
|
66
67
|
}
|
|
67
68
|
function rootStack(app) {
|
|
@@ -105,7 +106,6 @@ function reflectRoutesDetailed(app, _opts) {
|
|
|
105
106
|
const seen = /* @__PURE__ */ new Set();
|
|
106
107
|
const routes = [];
|
|
107
108
|
for (const r of raw) {
|
|
108
|
-
if (r.method === "HEAD") continue;
|
|
109
109
|
const key2 = `${r.method} ${r.path}`;
|
|
110
110
|
if (seen.has(key2)) continue;
|
|
111
111
|
seen.add(key2);
|
|
@@ -123,6 +123,7 @@ var init_reflect = __esm({
|
|
|
123
123
|
init_route_canon();
|
|
124
124
|
VALID_METHODS = /* @__PURE__ */ new Set([
|
|
125
125
|
"GET",
|
|
126
|
+
"HEAD",
|
|
126
127
|
"POST",
|
|
127
128
|
"PUT",
|
|
128
129
|
"PATCH",
|
|
@@ -917,6 +918,7 @@ var ShutdownManager = class {
|
|
|
917
918
|
|
|
918
919
|
// src/core/tunnel.ts
|
|
919
920
|
import { spawn as nodeChildSpawn } from "node:child_process";
|
|
921
|
+
import { createRequire } from "node:module";
|
|
920
922
|
var REDACTED_TOKEN = "***REDACTED***";
|
|
921
923
|
var CLOUDFLARED_RUN_ARGS = [
|
|
922
924
|
"tunnel",
|
|
@@ -924,12 +926,12 @@ var CLOUDFLARED_RUN_ARGS = [
|
|
|
924
926
|
"run",
|
|
925
927
|
"--token"
|
|
926
928
|
];
|
|
927
|
-
var
|
|
928
|
-
"cloudflared",
|
|
929
|
-
"/
|
|
930
|
-
"/
|
|
931
|
-
"/
|
|
932
|
-
|
|
929
|
+
var CLOUDFLARED_BINARY_PACKAGES = {
|
|
930
|
+
"linux-x64": "@farthershore/cloudflared-linux-x64",
|
|
931
|
+
"linux-arm64": "@farthershore/cloudflared-linux-arm64",
|
|
932
|
+
"darwin-arm64": "@farthershore/cloudflared-darwin-arm64",
|
|
933
|
+
"darwin-x64": "@farthershore/cloudflared-darwin-x64"
|
|
934
|
+
};
|
|
933
935
|
var DEFAULT_BASE_BACKOFF_MS = 1e3;
|
|
934
936
|
var DEFAULT_MAX_BACKOFF_MS = 6e4;
|
|
935
937
|
var DEFAULT_BINARY = "cloudflared";
|
|
@@ -1047,15 +1049,24 @@ var CloudflaredSupervisor = class {
|
|
|
1047
1049
|
child.on("exit", (code, signal) => this.handleExit(code, signal));
|
|
1048
1050
|
}
|
|
1049
1051
|
/**
|
|
1050
|
-
* Resolve the cloudflared binary path.
|
|
1051
|
-
*
|
|
1052
|
+
* Resolve the cloudflared binary path. Order:
|
|
1053
|
+
* 1. an installed `@farthershore/cloudflared-<platform>` optional-dependency
|
|
1054
|
+
* matching process.platform+arch (the injected locator), then
|
|
1055
|
+
* 2. an explicit `binaryPath` supplied by the host, then
|
|
1056
|
+
* 3. the bare `cloudflared` name on PATH (resolved at spawn time).
|
|
1057
|
+
*
|
|
1058
|
+
* On an unsupported arch (no optional dep, no binaryPath) AND no usable PATH
|
|
1059
|
+
* fallback, this raises a clear, redacted error pointing at the sidecar — it
|
|
1060
|
+
* NEVER downloads a binary at runtime. (Cross-platform binary management is the
|
|
1061
|
+
* optional-dep packages' job, populated at publish time.)
|
|
1052
1062
|
*/
|
|
1053
1063
|
resolveBinary() {
|
|
1054
|
-
if (this.binaryPath) return this.binaryPath;
|
|
1055
1064
|
const located = this.locateBinary();
|
|
1056
1065
|
if (located) return located;
|
|
1066
|
+
if (this.binaryPath) return this.binaryPath;
|
|
1067
|
+
if (currentBinaryPackageName() !== null) return DEFAULT_BINARY;
|
|
1057
1068
|
throw new Error(
|
|
1058
|
-
"cloudflared binary not found \u2014 install
|
|
1069
|
+
"cloudflared binary not found for this platform/arch \u2014 install an @farthershore/cloudflared-<platform> package, supply binaryPath, or run the sidecar runner instead (no binary is downloaded at runtime)."
|
|
1059
1070
|
);
|
|
1060
1071
|
}
|
|
1061
1072
|
/** Pipe stdout/stderr to the logger with the tunnel token redacted. */
|
|
@@ -1184,8 +1195,30 @@ function nodeProcess() {
|
|
|
1184
1195
|
const proc = globalThis.process;
|
|
1185
1196
|
return proc && typeof proc.on === "function" ? proc : null;
|
|
1186
1197
|
}
|
|
1198
|
+
function currentBinaryPackageName() {
|
|
1199
|
+
const proc = globalThis.process;
|
|
1200
|
+
if (!proc?.platform || !proc.arch) return null;
|
|
1201
|
+
return CLOUDFLARED_BINARY_PACKAGES[`${proc.platform}-${proc.arch}`] ?? null;
|
|
1202
|
+
}
|
|
1187
1203
|
function defaultLocateBinary() {
|
|
1188
|
-
|
|
1204
|
+
const pkg = currentBinaryPackageName();
|
|
1205
|
+
if (!pkg) return null;
|
|
1206
|
+
try {
|
|
1207
|
+
const require2 = createRequire(import.meta.url);
|
|
1208
|
+
const manifestPath = require2.resolve(`${pkg}/package.json`);
|
|
1209
|
+
return resolvePackageBinary(require2, pkg, manifestPath);
|
|
1210
|
+
} catch {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
function resolvePackageBinary(require2, pkg, manifestPath) {
|
|
1215
|
+
const sep = manifestPath.includes("\\") ? "\\" : "/";
|
|
1216
|
+
const root = manifestPath.slice(0, manifestPath.lastIndexOf(sep));
|
|
1217
|
+
const manifest = require2(manifestPath);
|
|
1218
|
+
const binField = manifest.bin;
|
|
1219
|
+
const relative = typeof binField === "string" ? binField : binField?.cloudflared ?? `bin${sep}cloudflared`;
|
|
1220
|
+
const normalized = relative.replace(/^\.[\\/]/, "");
|
|
1221
|
+
return `${root}${sep}${normalized}`;
|
|
1189
1222
|
}
|
|
1190
1223
|
|
|
1191
1224
|
// src/core/verifyRequest.ts
|
|
@@ -1337,8 +1370,8 @@ function headerGetter(headers) {
|
|
|
1337
1370
|
|
|
1338
1371
|
// src/core/runtime.ts
|
|
1339
1372
|
var DEFAULT_CORE_URL = "https://core.farthershore.com";
|
|
1340
|
-
var SDK_VERSION = "0.8.
|
|
1341
|
-
var CONTRACTS_FP = "
|
|
1373
|
+
var SDK_VERSION = "0.8.2".length > 0 ? "0.8.2" : "0.0.0-dev";
|
|
1374
|
+
var CONTRACTS_FP = "5beebf042c7ce96d".length > 0 ? "5beebf042c7ce96d" : "0000000000000000";
|
|
1342
1375
|
var FartherShore = class {
|
|
1343
1376
|
bootstrapClient;
|
|
1344
1377
|
fetchImpl;
|
|
@@ -1418,7 +1451,7 @@ var FartherShore = class {
|
|
|
1418
1451
|
* The reflection code is dynamically imported so it stays OFF the per-request
|
|
1419
1452
|
* verification hot path (the runtime stays route-unaware there). The report is
|
|
1420
1453
|
* an OUTBOUND backend→core call (same channel as bootstrap/metering), so it
|
|
1421
|
-
* works for every transport (
|
|
1454
|
+
* works for every transport (direct / tunnel).
|
|
1422
1455
|
*
|
|
1423
1456
|
* Returns the reconcile result (or null if reflection is unavailable / boot
|
|
1424
1457
|
* reporting failed). v1 reflects Express; `app` omitted → no-op.
|
|
@@ -1493,11 +1526,11 @@ var FartherShore = class {
|
|
|
1493
1526
|
return config.verification.required;
|
|
1494
1527
|
}
|
|
1495
1528
|
/**
|
|
1496
|
-
* Start the
|
|
1497
|
-
* `
|
|
1529
|
+
* Start the embedded runner. For a `tunnel` backend whose runner is
|
|
1530
|
+
* `embedded`, this supervises `cloudflared` as a child process
|
|
1498
1531
|
* (spawned via the injected/default spawner) using the tunnel token from
|
|
1499
|
-
* bootstrap. For every other transport (`
|
|
1500
|
-
*
|
|
1532
|
+
* bootstrap. For every other transport (`direct`, or the `sidecar` runner)
|
|
1533
|
+
* it is a no-op — there is no SDK-managed process to run.
|
|
1501
1534
|
*
|
|
1502
1535
|
* Fail-open by default: a tunnel that cannot start does NOT crash the host app
|
|
1503
1536
|
* (request verification stays fail-closed regardless — a different axis).
|
|
@@ -1506,7 +1539,7 @@ var FartherShore = class {
|
|
|
1506
1539
|
if (this.tunnelOptions.enabled === false) return;
|
|
1507
1540
|
const config = await this.ensureBootstrapped();
|
|
1508
1541
|
const transport = config.transport;
|
|
1509
|
-
if (transport.mode !== "
|
|
1542
|
+
if (transport.mode !== "tunnel" || transport.runner !== "embedded") {
|
|
1510
1543
|
return;
|
|
1511
1544
|
}
|
|
1512
1545
|
const tunnelToken = transport.cloudflared?.tunnelToken;
|
|
@@ -1514,7 +1547,7 @@ var FartherShore = class {
|
|
|
1514
1547
|
if (this.tunnelOptions.failClosed) {
|
|
1515
1548
|
throw new FartherShoreError(
|
|
1516
1549
|
"invalid_token",
|
|
1517
|
-
"
|
|
1550
|
+
"embedded cloudflared runner requires a tunnel token from bootstrap"
|
|
1518
1551
|
);
|
|
1519
1552
|
}
|
|
1520
1553
|
return;
|
|
@@ -1551,8 +1584,8 @@ var FartherShore = class {
|
|
|
1551
1584
|
return buildHealthReport({
|
|
1552
1585
|
runtimeToken: this.runtimeToken.length > 0,
|
|
1553
1586
|
bootstrap: this.bootstrapped && config !== null,
|
|
1554
|
-
// Populated by the
|
|
1555
|
-
// fs.start() launches
|
|
1587
|
+
// Populated by the embedded-cloudflared supervisor (Slice 3). Null until
|
|
1588
|
+
// fs.start() launches an embedded tunnel; otherwise the supervisor state.
|
|
1556
1589
|
tunnel: this.tunnel ? this.tunnel.healthString() : null,
|
|
1557
1590
|
verification: this.verificationEnabled && config !== null,
|
|
1558
1591
|
metering: this.meteringClient !== null
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module";const require=__createRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/reflect/route-canon.ts
|
|
4
|
+
function normalizeSegment(seg) {
|
|
5
|
+
if (seg === "**") return "{__fs_rest}";
|
|
6
|
+
if (seg === "*") return "{splat}";
|
|
7
|
+
if (seg.startsWith(":")) return `{${seg.slice(1)}}`;
|
|
8
|
+
if (seg.startsWith("[") && seg.endsWith("]")) return `{${seg.slice(1, -1)}}`;
|
|
9
|
+
return seg;
|
|
10
|
+
}
|
|
11
|
+
function normalizePath(path) {
|
|
12
|
+
if (path === "/" || path === "") return "/";
|
|
13
|
+
const lead = path.startsWith("/");
|
|
14
|
+
const out = path.split("/").filter((s) => s.length > 0).map(normalizeSegment).join("/");
|
|
15
|
+
return lead ? `/${out}` : out;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/reflect/index.ts
|
|
19
|
+
var VALID_METHODS = /* @__PURE__ */ new Set([
|
|
20
|
+
"GET",
|
|
21
|
+
"HEAD",
|
|
22
|
+
"POST",
|
|
23
|
+
"PUT",
|
|
24
|
+
"PATCH",
|
|
25
|
+
"DELETE",
|
|
26
|
+
"OPTIONS"
|
|
27
|
+
]);
|
|
28
|
+
function methodsOf(route) {
|
|
29
|
+
const out = /* @__PURE__ */ new Set();
|
|
30
|
+
if (route.methods) {
|
|
31
|
+
for (const [m, on] of Object.entries(route.methods))
|
|
32
|
+
if (on) out.add(m.toUpperCase());
|
|
33
|
+
}
|
|
34
|
+
for (const s of route.stack ?? []) {
|
|
35
|
+
if (typeof s.method === "string") out.add(s.method.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
if (out.has("GET")) out.delete("HEAD");
|
|
38
|
+
return [...out].filter((m) => VALID_METHODS.has(m));
|
|
39
|
+
}
|
|
40
|
+
function rootStack(app) {
|
|
41
|
+
const a = app;
|
|
42
|
+
return a?.router?.stack ?? a?._router?.stack;
|
|
43
|
+
}
|
|
44
|
+
function prefixFromRegexp(re) {
|
|
45
|
+
if (!re) return void 0;
|
|
46
|
+
let s = re.source;
|
|
47
|
+
if (s === "^\\/?$" || s === "^\\/") return "";
|
|
48
|
+
s = s.replace(/^\^/, "").replace(/\\\/\?\(\?=\\\/\|\$\)$/, "").replace(/\(\?=\\\/\|\$\)$/, "").replace(/\\\/\?$/, "").replace(/\$$/, "");
|
|
49
|
+
s = s.replace(/\\\//g, "/");
|
|
50
|
+
if (/[()?:*+\[\]|]/.test(s)) return void 0;
|
|
51
|
+
return s.startsWith("/") ? s : `/${s}`;
|
|
52
|
+
}
|
|
53
|
+
function walk(stack, prefix, out, counters) {
|
|
54
|
+
for (const layer of stack) {
|
|
55
|
+
if (layer.route && typeof layer.route.path === "string") {
|
|
56
|
+
const path = normalizePath(`${prefix}${layer.route.path}`);
|
|
57
|
+
for (const method of methodsOf(layer.route)) {
|
|
58
|
+
out.push({ method, path });
|
|
59
|
+
}
|
|
60
|
+
} else if (layer.handle?.stack && Array.isArray(layer.handle.stack)) {
|
|
61
|
+
const sub = prefixFromRegexp(layer.regexp);
|
|
62
|
+
if (sub === void 0 && layer.name === "router") {
|
|
63
|
+
counters.unreflectable += layer.handle.stack.filter(
|
|
64
|
+
(l) => l.route
|
|
65
|
+
).length;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
walk(layer.handle.stack, `${prefix}${sub ?? ""}`, out, counters);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function reflectRoutesDetailed(app, _opts) {
|
|
73
|
+
const stack = rootStack(app);
|
|
74
|
+
if (!stack) return { routes: [], unreflectable: 0 };
|
|
75
|
+
const raw = [];
|
|
76
|
+
const counters = { unreflectable: 0 };
|
|
77
|
+
walk(stack, "", raw, counters);
|
|
78
|
+
const seen = /* @__PURE__ */ new Set();
|
|
79
|
+
const routes = [];
|
|
80
|
+
for (const r of raw) {
|
|
81
|
+
const key = `${r.method} ${r.path}`;
|
|
82
|
+
if (seen.has(key)) continue;
|
|
83
|
+
seen.add(key);
|
|
84
|
+
routes.push(r);
|
|
85
|
+
}
|
|
86
|
+
return { routes, unreflectable: counters.unreflectable };
|
|
87
|
+
}
|
|
88
|
+
function reflectRoutes(app, opts) {
|
|
89
|
+
return reflectRoutesDetailed(app, opts).routes;
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
reflectRoutes,
|
|
93
|
+
reflectRoutesDetailed
|
|
94
|
+
};
|
|
@@ -3,9 +3,9 @@ import type { ReconcileResult } from "../reflect/reconcile.js";
|
|
|
3
3
|
import { type MeterOptions } from "./metering.js";
|
|
4
4
|
import { type SpawnFn } from "./tunnel.js";
|
|
5
5
|
import { type FartherShoreRequestContext, type VerifyRequestInput } from "./verifyRequest.js";
|
|
6
|
-
/** Advanced opt-in tunnel config. The
|
|
6
|
+
/** Advanced opt-in tunnel config. The embedded runner is the default DX. */
|
|
7
7
|
export type FartherShoreTunnelOptions = {
|
|
8
|
-
/** Opt out of the
|
|
8
|
+
/** Opt out of the embedded cloudflared runner (e.g. sidecar mode). */
|
|
9
9
|
enabled?: boolean;
|
|
10
10
|
/** Injected spawner (tests/non-default hosts). Defaults to node:child_process. */
|
|
11
11
|
spawn?: SpawnFn;
|
|
@@ -35,7 +35,7 @@ export type FartherShoreInitOptions = {
|
|
|
35
35
|
metering?: {
|
|
36
36
|
enabled?: boolean;
|
|
37
37
|
};
|
|
38
|
-
/**
|
|
38
|
+
/** Embedded-cloudflared runner config (advanced opt-in; default DX is on). */
|
|
39
39
|
tunnel?: FartherShoreTunnelOptions;
|
|
40
40
|
/** SDK metadata forwarded to bootstrap. */
|
|
41
41
|
instanceId?: string;
|
|
@@ -73,7 +73,7 @@ export declare class FartherShore {
|
|
|
73
73
|
* The reflection code is dynamically imported so it stays OFF the per-request
|
|
74
74
|
* verification hot path (the runtime stays route-unaware there). The report is
|
|
75
75
|
* an OUTBOUND backend→core call (same channel as bootstrap/metering), so it
|
|
76
|
-
* works for every transport (
|
|
76
|
+
* works for every transport (direct / tunnel).
|
|
77
77
|
*
|
|
78
78
|
* Returns the reconcile result (or null if reflection is unavailable / boot
|
|
79
79
|
* reporting failed). v1 reflects Express; `app` omitted → no-op.
|
|
@@ -89,11 +89,11 @@ export declare class FartherShore {
|
|
|
89
89
|
/** Whether verification is required (bootstrap × opt-out). */
|
|
90
90
|
verificationRequired(): Promise<boolean>;
|
|
91
91
|
/**
|
|
92
|
-
* Start the
|
|
93
|
-
* `
|
|
92
|
+
* Start the embedded runner. For a `tunnel` backend whose runner is
|
|
93
|
+
* `embedded`, this supervises `cloudflared` as a child process
|
|
94
94
|
* (spawned via the injected/default spawner) using the tunnel token from
|
|
95
|
-
* bootstrap. For every other transport (`
|
|
96
|
-
*
|
|
95
|
+
* bootstrap. For every other transport (`direct`, or the `sidecar` runner)
|
|
96
|
+
* it is a no-op — there is no SDK-managed process to run.
|
|
97
97
|
*
|
|
98
98
|
* Fail-open by default: a tunnel that cannot start does NOT crash the host app
|
|
99
99
|
* (request verification stays fail-closed regardless — a different axis).
|
|
@@ -121,8 +121,16 @@ export declare class CloudflaredSupervisor {
|
|
|
121
121
|
healthString(): string;
|
|
122
122
|
private spawnChild;
|
|
123
123
|
/**
|
|
124
|
-
* Resolve the cloudflared binary path.
|
|
125
|
-
*
|
|
124
|
+
* Resolve the cloudflared binary path. Order:
|
|
125
|
+
* 1. an installed `@farthershore/cloudflared-<platform>` optional-dependency
|
|
126
|
+
* matching process.platform+arch (the injected locator), then
|
|
127
|
+
* 2. an explicit `binaryPath` supplied by the host, then
|
|
128
|
+
* 3. the bare `cloudflared` name on PATH (resolved at spawn time).
|
|
129
|
+
*
|
|
130
|
+
* On an unsupported arch (no optional dep, no binaryPath) AND no usable PATH
|
|
131
|
+
* fallback, this raises a clear, redacted error pointing at the sidecar — it
|
|
132
|
+
* NEVER downloads a binary at runtime. (Cross-platform binary management is the
|
|
133
|
+
* optional-dep packages' job, populated at publish time.)
|
|
126
134
|
*/
|
|
127
135
|
private resolveBinary;
|
|
128
136
|
/** Pipe stdout/stderr to the logger with the tunnel token redacted. */
|
|
@@ -51,8 +51,8 @@ export declare const RUNTIME_BOOTSTRAP_CONTRACT: {
|
|
|
51
51
|
readonly perEventMax: "number";
|
|
52
52
|
};
|
|
53
53
|
readonly transport: {
|
|
54
|
-
readonly mode: "
|
|
55
|
-
readonly runner: "
|
|
54
|
+
readonly mode: "direct | tunnel";
|
|
55
|
+
readonly runner: "embedded | sidecar | null";
|
|
56
56
|
readonly originUrl: "string?";
|
|
57
57
|
readonly originHostname: "string?";
|
|
58
58
|
readonly localTarget: "string?";
|
|
@@ -214,15 +214,14 @@ export declare const RUNTIME_HEALTH_CONTRACT: {
|
|
|
214
214
|
};
|
|
215
215
|
export declare const RUNTIME_TRANSPORT_CONTRACT: {
|
|
216
216
|
readonly modes: {
|
|
217
|
-
readonly
|
|
218
|
-
readonly
|
|
219
|
-
readonly cloudflare_tunnel: "Farther Shore provisions a private outbound Cloudflare Tunnel; no inbound port. The only Production-secure tier. Consumes Cloudflare tunnel/route slots.";
|
|
217
|
+
readonly direct: "Gateway fetches the builder's public origin URL; the SDK middleware fail-closed-verifies every request via Ed25519 request signing. Provisions zero Cloudflare objects. Available on all tiers; also the dev path.";
|
|
218
|
+
readonly tunnel: "Farther Shore provisions a private outbound Cloudflare Tunnel; no inbound port. The Production-secure tier. Consumes Cloudflare tunnel/route slots.";
|
|
220
219
|
};
|
|
221
220
|
readonly runners: {
|
|
222
|
-
readonly
|
|
221
|
+
readonly embedded: "fs.start() supervises cloudflared as a child process (default DX).";
|
|
223
222
|
readonly sidecar: "Vanilla cloudflare/cloudflared container beside the app (production / non-Node).";
|
|
224
223
|
};
|
|
225
|
-
readonly channelTrust: readonly ["
|
|
224
|
+
readonly channelTrust: readonly ["tunnel"];
|
|
226
225
|
readonly requestTrust: "x-fs-signature";
|
|
227
|
-
readonly invariant: "Channel trust (
|
|
226
|
+
readonly invariant: "Channel trust (tunnel) and request trust (the X-FS-* signature) are distinct layers; both always apply. CF-Access-* headers are transport-layer only and are IGNORED by the SDK.";
|
|
228
227
|
};
|
|
@@ -103,8 +103,8 @@ export type CanonicalSigningInput = {
|
|
|
103
103
|
policyVersion: string;
|
|
104
104
|
};
|
|
105
105
|
export type RuntimeEnvironmentKind = RuntimeTokenKind;
|
|
106
|
-
export type TransportMode = "
|
|
107
|
-
export type TransportRunner = "
|
|
106
|
+
export type TransportMode = "direct" | "tunnel";
|
|
107
|
+
export type TransportRunner = "embedded" | "sidecar";
|
|
108
108
|
export type RuntimeBootstrapRequest = {
|
|
109
109
|
instanceId?: string;
|
|
110
110
|
sdkVersion?: string;
|
|
@@ -135,7 +135,6 @@ export type RuntimeTransportConfig = {
|
|
|
135
135
|
tunnelToken: string;
|
|
136
136
|
version: string;
|
|
137
137
|
};
|
|
138
|
-
mtlsClientCertRef?: string;
|
|
139
138
|
};
|
|
140
139
|
export type RuntimeRouteDescriptor = {
|
|
141
140
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farthershore/backend",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Farther Shore backend SDK for builder upstreams: signed response usage, fail-closed gateway request verification, health, and lifecycle from FS_RUNTIME_TOKEN",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,6 +31,12 @@
|
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
|
+
"optionalDependencies": {
|
|
35
|
+
"@farthershore/cloudflared-linux-x64": "0.0.0",
|
|
36
|
+
"@farthershore/cloudflared-linux-arm64": "0.0.0",
|
|
37
|
+
"@farthershore/cloudflared-darwin-arm64": "0.0.0",
|
|
38
|
+
"@farthershore/cloudflared-darwin-x64": "0.0.0"
|
|
39
|
+
},
|
|
34
40
|
"peerDependencies": {
|
|
35
41
|
"express": "^4.0.0 || ^5.0.0"
|
|
36
42
|
},
|