@rstreamlabs/tunnels 3.8.1 → 3.8.3

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/index.js CHANGED
@@ -1766,15 +1766,61 @@ var RstreamTunnelsResource = class {
1766
1766
  };
1767
1767
 
1768
1768
  // src/tunnels.ts
1769
- var import_rstream11 = require("@rstreamlabs/rstream");
1770
1769
  var import_rstream12 = require("@rstreamlabs/rstream");
1771
1770
  var import_rstream13 = require("@rstreamlabs/rstream");
1771
+ var import_rstream14 = require("@rstreamlabs/rstream");
1772
1772
 
1773
1773
  // src/turn.ts
1774
1774
  var import_rstream8 = require("@rstreamlabs/rstream");
1775
1775
  var import_rstream9 = require("@rstreamlabs/rstream");
1776
1776
  var import_rstream10 = require("@rstreamlabs/rstream");
1777
1777
  var import_turn = require("@rstreamlabs/rstream/turn");
1778
+
1779
+ // src/control-plane-fetch.ts
1780
+ function createRequestLifetime(parent, timeoutMilliseconds) {
1781
+ const controller = new AbortController();
1782
+ const abort = () => controller.abort(parent.reason);
1783
+ if (parent.aborted) {
1784
+ abort();
1785
+ } else {
1786
+ parent.addEventListener("abort", abort, { once: true });
1787
+ }
1788
+ const timeout = setTimeout(
1789
+ () => controller.abort(new Error("Control plane request timed out.")),
1790
+ timeoutMilliseconds
1791
+ );
1792
+ return {
1793
+ close: () => {
1794
+ clearTimeout(timeout);
1795
+ parent.removeEventListener("abort", abort);
1796
+ },
1797
+ signal: controller.signal
1798
+ };
1799
+ }
1800
+ function createBoundedControlPlaneFetch(timeoutMilliseconds) {
1801
+ return async (input, init) => {
1802
+ const request = new Request(input, init);
1803
+ const headers = new Headers(request.headers);
1804
+ headers.set("Connection", "close");
1805
+ const lifetime = createRequestLifetime(request.signal, timeoutMilliseconds);
1806
+ try {
1807
+ const response = await globalThis.fetch(request, {
1808
+ headers,
1809
+ signal: lifetime.signal
1810
+ });
1811
+ const responseBody = response.body === null ? null : new Uint8Array(await response.arrayBuffer());
1812
+ return new Response(responseBody, {
1813
+ headers: Object.fromEntries(response.headers.entries()),
1814
+ status: response.status,
1815
+ statusText: response.statusText
1816
+ });
1817
+ } finally {
1818
+ lifetime.close();
1819
+ }
1820
+ };
1821
+ }
1822
+
1823
+ // src/turn.ts
1778
1824
  var z8 = __toESM(require("zod"));
1779
1825
  var import_crypto = __toESM(require("crypto"));
1780
1826
  var defaultTURNPort = 3478;
@@ -1782,6 +1828,8 @@ var defaultTURNSPort = 5349;
1782
1828
  var defaultTURNCredentialTTLSeconds = 10 * 60;
1783
1829
  var maxTURNCredentialTTLSeconds = 60 * 60;
1784
1830
  var maxTURNKeyringBytes = 8 * 1024;
1831
+ var defaultAPIRequestTimeoutMilliseconds = 1e4;
1832
+ var maxAPIRequestTimeoutMilliseconds = 6e4;
1785
1833
  var turnTokenClaimsSchema = z8.object({
1786
1834
  exp: z8.number().int().optional(),
1787
1835
  iat: z8.number().int().optional(),
@@ -1815,6 +1863,15 @@ function normalizeTTLSeconds(ttlSeconds) {
1815
1863
  }
1816
1864
  return ttl;
1817
1865
  }
1866
+ function normalizeAPIRequestTimeoutMilliseconds(timeoutMilliseconds) {
1867
+ const timeout = timeoutMilliseconds ?? defaultAPIRequestTimeoutMilliseconds;
1868
+ if (!Number.isInteger(timeout) || timeout < 1 || timeout > maxAPIRequestTimeoutMilliseconds) {
1869
+ throw new Error(
1870
+ "TURN API request timeout must be an integer between 1 and 60000 milliseconds."
1871
+ );
1872
+ }
1873
+ return timeout;
1874
+ }
1818
1875
  function createAPITURNCredentialsParams(ttlSeconds) {
1819
1876
  return ttlSeconds === void 0 ? {} : { ttlSeconds: normalizeTTLSeconds(ttlSeconds) };
1820
1877
  }
@@ -1831,14 +1888,14 @@ function normalizeTURNPort(port) {
1831
1888
  function normalizeTURNSPort(port) {
1832
1889
  return normalizePort(port, defaultTURNSPort, "TURNS port");
1833
1890
  }
1834
- function createTURNURLs(clusterDomain, turnPort, turnsPort) {
1891
+ function createTURNURLs(turnDomain, turnPort, turnsPort) {
1835
1892
  const normalizedTURNPort = normalizeTURNPort(turnPort);
1836
1893
  const normalizedTURNSPort = normalizeTURNSPort(turnsPort);
1837
1894
  return [
1838
- `turn:${clusterDomain}:${normalizedTURNPort}?transport=udp`,
1839
- `turn:${clusterDomain}:${normalizedTURNPort}?transport=tcp`,
1840
- `turns:${clusterDomain}:${normalizedTURNSPort}?transport=udp`,
1841
- `turns:${clusterDomain}:${normalizedTURNSPort}?transport=tcp`
1895
+ `turn:${turnDomain}:${normalizedTURNPort}?transport=udp`,
1896
+ `turn:${turnDomain}:${normalizedTURNPort}?transport=tcp`,
1897
+ `turns:${turnDomain}:${normalizedTURNSPort}?transport=udp`,
1898
+ `turns:${turnDomain}:${normalizedTURNSPort}?transport=tcp`
1842
1899
  ];
1843
1900
  }
1844
1901
  function parseTURNTokenClaims(token) {
@@ -2008,11 +2065,16 @@ async function createAPITURNCredentials(options) {
2008
2065
  "Project ID or project endpoint is required for TURN API mode."
2009
2066
  );
2010
2067
  }
2068
+ const requestTimeoutMilliseconds = normalizeAPIRequestTimeoutMilliseconds(
2069
+ options.requestTimeoutMilliseconds
2070
+ );
2071
+ const params = createAPITURNCredentialsParams(options.ttlSeconds);
2072
+ const request = options.fetch ?? createBoundedControlPlaneFetch(requestTimeoutMilliseconds);
2011
2073
  const client = new import_rstream10.RstreamClient({
2012
2074
  apiUrl: options.apiUrl,
2013
- credentials: options.credentials
2075
+ credentials: options.credentials,
2076
+ fetch: request
2014
2077
  });
2015
- const params = createAPITURNCredentialsParams(options.ttlSeconds);
2016
2078
  if (projectId) {
2017
2079
  return await client.tunnels.projects.createTurnCredentials(
2018
2080
  projectId,
@@ -2030,9 +2092,14 @@ async function createAPITURNCredentials(options) {
2030
2092
  );
2031
2093
  }
2032
2094
  function createPATTURNCredentials(options) {
2033
- const clusterDomain = normalizeClusterDomain(options.clusterDomain);
2034
- if (!clusterDomain) {
2035
- throw new Error("Cluster domain is required for TURN PAT mode.");
2095
+ const legacyDomain = normalizeClusterDomain(options.clusterDomain);
2096
+ const turnDomain = normalizeClusterDomain(options.turnDomain) ?? legacyDomain;
2097
+ const turnRealm = normalizeClusterDomain(options.turnRealm) ?? legacyDomain;
2098
+ if (!turnDomain) {
2099
+ throw new Error("TURN domain is required for TURN PAT mode.");
2100
+ }
2101
+ if (!turnRealm) {
2102
+ throw new Error("TURN realm is required for TURN PAT mode.");
2036
2103
  }
2037
2104
  const projectEndpoint = normalizeOptionalString2(options.projectEndpoint);
2038
2105
  if (!projectEndpoint) {
@@ -2046,12 +2113,19 @@ function createPATTURNCredentials(options) {
2046
2113
  if (claims.type !== "pat") {
2047
2114
  throw new Error("TURN PAT mode requires a PAT token.");
2048
2115
  }
2049
- const tokenEndpoint = normalizeOptionalString2(options.tokenEndpoint);
2116
+ const claimsTokenEndpoint = normalizeOptionalString2(
2117
+ claims.token_endpoint ?? claims.tokendpoint
2118
+ );
2119
+ const configuredTokenEndpoint = normalizeOptionalString2(
2120
+ options.tokenEndpoint
2121
+ );
2122
+ const tokenEndpoint = configuredTokenEndpoint ?? claimsTokenEndpoint;
2050
2123
  if (!tokenEndpoint) {
2051
- throw new Error("Token endpoint is required for TURN PAT mode.");
2124
+ throw new Error(
2125
+ "TURN PAT mode requires a PAT token carrying a token endpoint."
2126
+ );
2052
2127
  }
2053
- const claimsTokenEndpoint = claims.token_endpoint ?? claims.tokendpoint;
2054
- if (claimsTokenEndpoint !== void 0 && claimsTokenEndpoint !== tokenEndpoint) {
2128
+ if (configuredTokenEndpoint !== void 0 && claimsTokenEndpoint !== void 0 && claimsTokenEndpoint !== configuredTokenEndpoint) {
2055
2129
  throw new Error("Token endpoint does not match the PAT token.");
2056
2130
  }
2057
2131
  const now = normalizeNow(options.now);
@@ -2064,7 +2138,7 @@ function createPATTURNCredentials(options) {
2064
2138
  import_crypto.default.hkdfSync(
2065
2139
  "sha256",
2066
2140
  tokenHash,
2067
- Buffer.from(clusterDomain, "utf8"),
2141
+ Buffer.from(turnRealm, "utf8"),
2068
2142
  Buffer.from("turn-pat-v1", "utf8"),
2069
2143
  32
2070
2144
  )
@@ -2073,14 +2147,19 @@ function createPATTURNCredentials(options) {
2073
2147
  return import_turn.turnCredentialsSchema.parse({
2074
2148
  credential,
2075
2149
  ttl,
2076
- urls: createTURNURLs(clusterDomain, options.turnPort, options.turnsPort),
2150
+ urls: createTURNURLs(turnDomain, options.turnPort, options.turnsPort),
2077
2151
  username
2078
2152
  });
2079
2153
  }
2080
2154
  async function createAPPTURNCredentials(options) {
2081
- const clusterDomain = normalizeClusterDomain(options.clusterDomain);
2082
- if (!clusterDomain) {
2083
- throw new Error("Cluster domain is required for TURN APP mode.");
2155
+ const legacyDomain = normalizeClusterDomain(options.clusterDomain);
2156
+ const turnDomain = normalizeClusterDomain(options.turnDomain) ?? legacyDomain;
2157
+ const turnRealm = normalizeClusterDomain(options.turnRealm) ?? legacyDomain;
2158
+ if (!turnDomain) {
2159
+ throw new Error("TURN domain is required for TURN APP mode.");
2160
+ }
2161
+ if (!turnRealm) {
2162
+ throw new Error("TURN realm is required for TURN APP mode.");
2084
2163
  }
2085
2164
  const projectEndpoint = normalizeOptionalString2(options.projectEndpoint);
2086
2165
  if (!projectEndpoint) {
@@ -2098,7 +2177,7 @@ async function createAPPTURNCredentials(options) {
2098
2177
  const exp = normalizeNow(options.now) + ttlSeconds;
2099
2178
  const username = `v1:${exp}:app:${projectEndpoint}:${clientId}`;
2100
2179
  const clientPrivateKey = parseAPPPrivateKey(clientSecret);
2101
- const serverPublicKeyHex = normalizeOptionalString2(options.serverPublicKeyHex) ?? await loadTURNServerPublicKeyHex(clusterDomain, options);
2180
+ const serverPublicKeyHex = normalizeOptionalString2(options.serverPublicKeyHex) ?? await loadTURNServerPublicKeyHex(turnRealm, options);
2102
2181
  const serverPublicKey = parseTURNServerPublicKeyHex(serverPublicKeyHex);
2103
2182
  requireMatchingCurve(clientPrivateKey, serverPublicKey);
2104
2183
  const sharedSecret = import_crypto.default.diffieHellman({
@@ -2109,7 +2188,7 @@ async function createAPPTURNCredentials(options) {
2109
2188
  import_crypto.default.hkdfSync(
2110
2189
  "sha256",
2111
2190
  sharedSecret,
2112
- Buffer.from(clusterDomain, "utf8"),
2191
+ Buffer.from(turnRealm, "utf8"),
2113
2192
  Buffer.from("turn-app-v1", "utf8"),
2114
2193
  32
2115
2194
  )
@@ -2118,7 +2197,7 @@ async function createAPPTURNCredentials(options) {
2118
2197
  return import_turn.turnCredentialsSchema.parse({
2119
2198
  credential,
2120
2199
  ttl: ttlSeconds,
2121
- urls: createTURNURLs(clusterDomain, options.turnPort, options.turnsPort),
2200
+ urls: createTURNURLs(turnDomain, options.turnPort, options.turnsPort),
2122
2201
  username
2123
2202
  });
2124
2203
  }
@@ -2136,33 +2215,38 @@ async function createTURNCredentials(options) {
2136
2215
  return await createAPITURNCredentials({
2137
2216
  apiUrl: options.apiUrl,
2138
2217
  credentials: controlPlaneCredentials,
2218
+ fetch: options.fetch,
2139
2219
  projectEndpoint,
2140
2220
  projectId: options.projectId,
2221
+ requestTimeoutMilliseconds: options.requestTimeoutMilliseconds,
2141
2222
  ttlSeconds: options.ttlSeconds
2142
2223
  });
2143
2224
  }
2144
- const clusterDomain = normalizeClusterDomain(options.clusterDomain) ?? (projectEndpoint && options.engine ? deriveClusterDomainFromEngine(projectEndpoint, options.engine) : void 0);
2225
+ const legacyDomain = normalizeClusterDomain(options.clusterDomain);
2226
+ const turnDomain = normalizeClusterDomain(options.turnDomain) ?? legacyDomain ?? (projectEndpoint && options.engine ? deriveClusterDomainFromEngine(projectEndpoint, options.engine) : void 0);
2227
+ const turnRealm = normalizeClusterDomain(options.turnRealm) ?? legacyDomain;
2145
2228
  if (mode === "pat") {
2146
2229
  if (!(0, import_rstream9.isTokenCredentials)(credentials)) {
2147
2230
  throw new Error("A PAT token is required for TURN PAT mode.");
2148
2231
  }
2149
2232
  return createPATTURNCredentials({
2150
- clusterDomain: requireOption(
2151
- clusterDomain,
2152
- "Cluster domain is required for TURN PAT mode."
2153
- ),
2154
2233
  now: options.now,
2155
2234
  projectEndpoint: requireOption(
2156
2235
  projectEndpoint,
2157
2236
  "Project endpoint is required for TURN PAT mode."
2158
2237
  ),
2159
2238
  token: credentials.token,
2160
- tokenEndpoint: requireOption(
2161
- normalizeOptionalString2(options.tokenEndpoint),
2162
- "Token endpoint is required for TURN PAT mode."
2163
- ),
2239
+ tokenEndpoint: options.tokenEndpoint,
2164
2240
  ttlSeconds: options.ttlSeconds,
2241
+ turnDomain: requireOption(
2242
+ turnDomain,
2243
+ "TURN domain is required for TURN PAT mode."
2244
+ ),
2165
2245
  turnPort: options.turnPort,
2246
+ turnRealm: requireOption(
2247
+ turnRealm,
2248
+ "TURN realm is required for TURN PAT mode."
2249
+ ),
2166
2250
  turnsPort: options.turnsPort
2167
2251
  });
2168
2252
  }
@@ -2173,10 +2257,6 @@ async function createTURNCredentials(options) {
2173
2257
  apiUrl: options.apiUrl,
2174
2258
  clientId: credentials.clientId,
2175
2259
  clientSecret: credentials.clientSecret,
2176
- clusterDomain: requireOption(
2177
- clusterDomain,
2178
- "Cluster domain is required for TURN APP mode."
2179
- ),
2180
2260
  keyringBaseUrl: options.keyringBaseUrl,
2181
2261
  now: options.now,
2182
2262
  projectEndpoint: requireOption(
@@ -2185,24 +2265,36 @@ async function createTURNCredentials(options) {
2185
2265
  ),
2186
2266
  serverPublicKeyHex: options.serverPublicKeyHex,
2187
2267
  ttlSeconds: options.ttlSeconds,
2268
+ turnDomain: requireOption(
2269
+ turnDomain,
2270
+ "TURN domain is required for TURN APP mode."
2271
+ ),
2188
2272
  turnPort: options.turnPort,
2273
+ turnRealm: requireOption(
2274
+ turnRealm,
2275
+ "TURN realm is required for TURN APP mode."
2276
+ ),
2189
2277
  turnsPort: options.turnsPort
2190
2278
  });
2191
2279
  }
2192
2280
 
2193
2281
  // src/turn-resource.ts
2282
+ var import_rstream11 = require("@rstreamlabs/rstream");
2194
2283
  var RstreamTURNResource = class {
2195
2284
  client;
2196
2285
  constructor(client) {
2197
2286
  this.client = client;
2198
2287
  }
2199
2288
  async createCredentials(options = {}) {
2200
- const engine = options.engine ?? (options.clusterDomain === void 0 ? await this.client.getEngine().catch(() => void 0) : void 0);
2289
+ const credentials = options.credentials ?? this.client.credentials;
2290
+ const localMode = options.mode === "app" || options.mode === "pat" || options.mode === void 0 && (0, import_rstream11.isClientCredentials)(credentials);
2291
+ const target = localMode && (options.turnDomain === void 0 || options.turnRealm === void 0) && options.clusterDomain === void 0 ? await this.client.getTURNTarget() : void 0;
2292
+ const engine = options.engine ?? (options.clusterDomain === void 0 && options.turnDomain === void 0 && target === void 0 ? await this.client.getEngine().catch(() => void 0) : void 0);
2201
2293
  return await createTURNCredentials({
2202
2294
  apiUrl: options.apiUrl ?? this.client.apiUrl,
2203
2295
  clusterDomain: options.clusterDomain,
2204
2296
  controlPlaneCredentials: options.controlPlaneCredentials ?? this.client.controlPlaneCredentials,
2205
- credentials: options.credentials ?? this.client.credentials,
2297
+ credentials,
2206
2298
  engine,
2207
2299
  keyringBaseUrl: options.keyringBaseUrl,
2208
2300
  mode: options.mode,
@@ -2212,8 +2304,10 @@ var RstreamTURNResource = class {
2212
2304
  serverPublicKeyHex: options.serverPublicKeyHex,
2213
2305
  tokenEndpoint: options.tokenEndpoint,
2214
2306
  ttlSeconds: options.ttlSeconds,
2215
- turnPort: options.turnPort,
2216
- turnsPort: options.turnsPort
2307
+ turnDomain: options.turnDomain ?? target?.turnDomain,
2308
+ turnPort: options.turnPort ?? target?.turnPort,
2309
+ turnRealm: options.turnRealm ?? target?.turnRealm,
2310
+ turnsPort: options.turnsPort ?? target?.turnsPort
2217
2311
  });
2218
2312
  }
2219
2313
  };
@@ -2394,7 +2488,7 @@ var RstreamTunnelsClient = class {
2394
2488
  this.config?.projectEndpoint
2395
2489
  );
2396
2490
  if (region !== void 0) {
2397
- if (normalizeOptionalString3(this.config?.engine) !== void 0 || (0, import_rstream13.readEnvironment)().engine !== void 0) {
2491
+ if (normalizeOptionalString3(this.config?.engine) !== void 0 || (0, import_rstream14.readEnvironment)().engine !== void 0) {
2398
2492
  throw new Error(
2399
2493
  "Region selection cannot be combined with an explicit engine override."
2400
2494
  );
@@ -2406,17 +2500,17 @@ var RstreamTunnelsClient = class {
2406
2500
  }
2407
2501
  const project = await this.getManagedProject();
2408
2502
  if (project !== void 0) {
2409
- const engine = (0, import_rstream11.getTunnelsProjectEngine)(project, region);
2503
+ const engine = (0, import_rstream12.getTunnelsProjectEngine)(project, region);
2410
2504
  if (engine) return normalizeEngineAddress(engine);
2411
2505
  }
2412
2506
  throw new Error(
2413
2507
  "Failed to resolve the selected regional engine from the managed tunnels project."
2414
2508
  );
2415
2509
  }
2416
- if (projectEndpoint !== void 0 && normalizeOptionalString3(this.config?.engine) === void 0 && (0, import_rstream13.readEnvironment)().engine === void 0) {
2510
+ if (projectEndpoint !== void 0 && normalizeOptionalString3(this.config?.engine) === void 0 && (0, import_rstream14.readEnvironment)().engine === void 0) {
2417
2511
  const project = await this.getManagedProject();
2418
2512
  if (project !== void 0) {
2419
- const engine = (0, import_rstream11.getTunnelsProjectEngine)(project);
2513
+ const engine = (0, import_rstream12.getTunnelsProjectEngine)(project);
2420
2514
  if (engine) {
2421
2515
  return normalizeEngineAddress(engine);
2422
2516
  }
@@ -2435,6 +2529,27 @@ var RstreamTunnelsClient = class {
2435
2529
  region: this.config?.region
2436
2530
  });
2437
2531
  }
2532
+ async getTURNTarget() {
2533
+ const project = await this.getManagedProject();
2534
+ if (project === void 0) {
2535
+ throw new Error(
2536
+ "Managed project endpoint is required to resolve the TURN target."
2537
+ );
2538
+ }
2539
+ const turnDomain = project.turnDomain?.trim() || project.domain.trim();
2540
+ const turnRealm = project.turnRealm?.trim();
2541
+ if (!turnRealm) {
2542
+ throw new Error(
2543
+ "Managed project metadata does not expose the TURN realm; use API credential mode or provide turnRealm explicitly."
2544
+ );
2545
+ }
2546
+ return {
2547
+ turnDomain,
2548
+ turnPort: project.turnPort,
2549
+ turnRealm,
2550
+ turnsPort: project.turnsPort
2551
+ };
2552
+ }
2438
2553
  async getManagedProject() {
2439
2554
  const projectEndpoint = normalizeOptionalString3(
2440
2555
  this.config?.projectEndpoint
@@ -2511,7 +2626,7 @@ var RstreamTunnelsClient = class {
2511
2626
  if (!credentials) {
2512
2627
  return void 0;
2513
2628
  }
2514
- if (!(0, import_rstream12.isClientCredentials)(credentials)) {
2629
+ if (!(0, import_rstream13.isClientCredentials)(credentials)) {
2515
2630
  return credentials.token;
2516
2631
  }
2517
2632
  const tokenParams = await this.getEngineAuthTokenParams();