@camstack/server 1.1.61 → 1.1.62

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.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enrichInputWithUserAgent = enrichInputWithUserAgent;
4
+ exports.enrichInputWithRelayClass = enrichInputWithRelayClass;
4
5
  exports.wrapWebrtcSessionProviderWithRelay = wrapWebrtcSessionProviderWithRelay;
5
6
  exports.buildAppRouter = buildAppRouter;
6
7
  const addon_settings_router_js_1 = require("../core/addon-settings.router.js");
@@ -39,30 +40,93 @@ function enrichInputWithUserAgent(input, userAgent) {
39
40
  };
40
41
  }
41
42
  /**
42
- * Relay-only forcing for remote viewers is DISABLED (2026-05-26).
43
+ * Operator gate for FORCING TURN-relay-only ICE on genuinely-remote viewers.
44
+ * DEFAULT OFF.
43
45
  *
44
- * It was meant to give CGNAT/4G viewers a clean relay↔relay path, but werift's
45
- * TURN media-forward is unreliable between two real TURN servers (relay↔relay
46
- * connects yet media never arrives → connected-but-black), and forcing relay
47
- * ALSO kills the direct LAN/Tailscale host pair which carries full native
48
- * quality with no relay. We now offer ALL candidates (host incl. the hub's
49
- * advertised Tailscale address, srflx, relay) and let ICE nominate the best
50
- * reachable pair: direct when possible, relay only as a fallback. The
51
- * `relayOnly` cap field + broker support remain for when relay media-forward
52
- * is fixed.
46
+ * The forced relay↔relay path is disabled by default because it was NEVER
47
+ * validated to deliver media. It was originally disabled on 2026-05-26 after a
48
+ * connected relay session produced a black picture ICE/DTLS completed but no
49
+ * decodable media was forwarded over the relay (a MEDIA-FORWARD reliability
50
+ * bug, not a signalling one). The later werift+0.22.9 patch
51
+ * (`patches/werift+0.22.9.patch`) fixed a DIFFERENT bug a host/srflx
52
+ * candidate LEAK in the forced-relay SDP and does nothing for relay
53
+ * media-forward, so the connected-but-black regression remains unproven-fixed.
53
54
  *
54
- * The wrapper additionally enriches the `createSession` / `handleOffer`
55
- * subscriber attribution with the originating client's User-Agent, read
56
- * from the tRPC request context (browser sessions). All OTHER methods
57
- * delegate straight through auth, the remote-proxy factory and every
58
- * signaling behaviour are untouched.
55
+ * Until an operator confirms that ONE real remote relay session (4G /
56
+ * Cloudflare-tunnel) actually delivers media, remote viewers stay on the same
57
+ * `relayOnly:false` path as everyone else. Set `CAMSTACK_FORCE_RELAY_REMOTE=1`
58
+ * (or `true`) to re-enable remote→relay once validated; the broker's pairClass
59
+ * logging is the instrument for that validation.
60
+ */
61
+ const FORCE_RELAY_REMOTE = process.env.CAMSTACK_FORCE_RELAY_REMOTE === '1' ||
62
+ process.env.CAMSTACK_FORCE_RELAY_REMOTE === 'true';
63
+ /**
64
+ * Inject the SERVER-computed `relayOnly` ICE override.
65
+ *
66
+ * The client's three-way classification (`lan | vpn | remote`, see
67
+ * `client-ip.ts`) is ALWAYS computed by the caller and passed here so the
68
+ * broker's pairClass logging can observe the viewer class for every session.
69
+ * Whether that class actually forces a relay is gated by
70
+ * `FORCE_RELAY_REMOTE` (default OFF — see above):
71
+ * - gate OFF → `relayOnly: false` for ALL classes (today's effective,
72
+ * validated behaviour: nobody is forced onto the unproven relay path).
73
+ * - gate ON → `deriveRelayOnly(clientClass)` — only genuinely-remote
74
+ * viewers (public / CGNAT-4G / Cloudflare-tunnel) get `relayOnly: true`;
75
+ * `lan` and `vpn` (Tailscale) keep the low-latency direct host/srflx path.
76
+ *
77
+ * When the class is `null` (mesh-originated call — no request context) the
78
+ * input passes through UNCHANGED so a trusted addon-set `relayOnly` (e.g.
79
+ * Alexa) is never clobbered. Any client-supplied `relayOnly` on a
80
+ * request-bound call IS overwritten — the hub trusts only the request context.
81
+ *
82
+ * Immutable — builds a NEW input object, never mutates the caller's.
83
+ */
84
+ function enrichInputWithRelayClass(input, clientClass) {
85
+ if (clientClass === null)
86
+ return input;
87
+ const relayOnly = FORCE_RELAY_REMOTE ? (0, client_ip_js_1.deriveRelayOnly)(clientClass) : false;
88
+ return { ...input, relayOnly };
89
+ }
90
+ /**
91
+ * Per-request wrapper around the resolved `webrtc-session` broker singleton.
92
+ *
93
+ * Two SERVER-side enrichments, both read from the tRPC request context (the
94
+ * broker is a forked addon that cannot see the HTTP request):
95
+ * 1. `relayOnly` — the ICE policy. The client is classified three ways
96
+ * (`lan | vpn | remote`, 2026-07-15): Tailscale (`vpn`) is distinguished
97
+ * from genuine internet (`remote`) and Cloudflare Tunnel's
98
+ * `CF-Connecting-IP` on the loopback connector path is honoured, closing
99
+ * the two blind spots the boolean classifier had (a tailnet client on the
100
+ * hub's own 100.64/10 address is `vpn`; a Cloudflare-tunneled viewer with
101
+ * a loopback socket peer is `remote`, not misread as LAN). The class is
102
+ * ALWAYS computed and threaded so the broker's pairClass logging can
103
+ * observe it — but FORCING remote viewers onto a relay↔relay path is
104
+ * GATED OFF by default (`CAMSTACK_FORCE_RELAY_REMOTE`, see
105
+ * `enrichInputWithRelayClass`).
106
+ *
107
+ * Why gated: the forced-relay MEDIA-FORWARD path was disabled on
108
+ * 2026-05-26 because a connected relay session delivered NO decodable
109
+ * media (ICE/DTLS up, picture black). The werift+0.22.9 patch
110
+ * (`patches/werift+0.22.9.patch`) fixed a DIFFERENT bug — the forced-relay
111
+ * SDP host/srflx candidate LEAK — and does NOT address relay
112
+ * media-forward, so the connected-but-black regression is still unproven-
113
+ * fixed. The flag lets the operator re-enable remote→relay only after
114
+ * validating that ONE real 4G / Cloudflare-tunnel relay session actually
115
+ * delivers media (the broker pairClass logging is the instrument). Until
116
+ * then every class gets `relayOnly:false`.
117
+ * 2. `consumerAttribution.userAgent` — the browser UA for the broker's
118
+ * client list.
119
+ *
120
+ * All OTHER methods delegate straight through — auth, the remote-proxy
121
+ * factory and every signaling behaviour are untouched.
59
122
  */
60
123
  function wrapWebrtcSessionProviderWithRelay(provider, ctx) {
61
124
  const userAgent = (0, client_ip_js_1.extractUserAgent)(ctx.req);
125
+ const clientClass = (0, client_ip_js_1.classifyClientRequest)(ctx.req);
62
126
  return {
63
127
  ...provider,
64
- createSession: (input) => provider.createSession(enrichInputWithUserAgent(input, userAgent)),
65
- handleOffer: (input) => provider.handleOffer(enrichInputWithUserAgent(input, userAgent)),
128
+ createSession: (input) => provider.createSession(enrichInputWithRelayClass(enrichInputWithUserAgent(input, userAgent), clientClass)),
129
+ handleOffer: (input) => provider.handleOffer(enrichInputWithRelayClass(enrichInputWithUserAgent(input, userAgent), clientClass)),
66
130
  };
67
131
  }
68
132
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.1.61",
3
+ "version": "1.1.62",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -23,19 +23,19 @@
23
23
  "test:watch": "vitest"
24
24
  },
25
25
  "dependencies": {
26
- "@camstack/addon-admin-ui": "1.1.50",
27
- "@camstack/addon-advanced-notifier": "1.1.23",
28
- "@camstack/addon-auth": "1.1.8",
26
+ "@camstack/addon-admin-ui": "1.1.51",
27
+ "@camstack/addon-advanced-notifier": "1.1.24",
28
+ "@camstack/addon-auth": "1.1.9",
29
29
  "@camstack/addon-decoder-nodeav": "1.1.11",
30
30
  "@camstack/addon-notifiers": "1.1.23",
31
- "@camstack/addon-pipeline": "1.1.55",
32
- "@camstack/addon-pipeline-orchestrator": "1.1.45",
33
- "@camstack/addon-post-analysis": "1.1.27",
31
+ "@camstack/addon-pipeline": "1.1.56",
32
+ "@camstack/addon-pipeline-orchestrator": "1.1.46",
33
+ "@camstack/addon-post-analysis": "1.1.28",
34
34
  "@camstack/sdk": "1.1.24",
35
35
  "@camstack/shm-ring": "1.0.23",
36
- "@camstack/system": "1.1.48",
37
- "@camstack/types": "1.1.43",
38
- "@camstack/ui-library": "1.1.35",
36
+ "@camstack/system": "1.1.49",
37
+ "@camstack/types": "1.1.44",
38
+ "@camstack/ui-library": "1.1.36",
39
39
  "@fastify/compress": "^9.0.0",
40
40
  "@fastify/cookie": "^11.0.2",
41
41
  "@fastify/multipart": "^10.0.0",