@rstreamlabs/tunnels 3.8.2 → 3.9.0

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
@@ -84,7 +84,9 @@ __export(index_exports, {
84
84
  resolveTunnelsCredentials: () => resolveTunnelsCredentials,
85
85
  resolveTunnelsEngine: () => resolveTunnelsEngine,
86
86
  resolveTunnelsRegion: () => resolveTunnelsRegion,
87
+ resolveWebTTYBrowserEndpoint: () => resolveWebTTYBrowserEndpoint,
87
88
  resolveWebTTYControlRequestParamsSchema: () => resolveWebTTYControlRequestParamsSchema,
89
+ resolveWebTTYServerTransport: () => resolveWebTTYServerTransport,
88
90
  signWebhookPayload: () => signWebhookPayload,
89
91
  streamEndpointSchema: () => streamEndpointSchema,
90
92
  streamEntrySchema: () => streamEntrySchema,
@@ -114,6 +116,7 @@ __export(index_exports, {
114
116
  webTTYTransportSchema: () => webTTYTransportSchema,
115
117
  webhookEventsSchema: () => webhookEventsSchema,
116
118
  webttyServerSchema: () => webttyServerSchema,
119
+ webttyTransportSchema: () => webttyTransportSchema,
117
120
  wsEventsSchema: () => wsEventsSchema
118
121
  });
119
122
  module.exports = __toCommonJS(index_exports);
@@ -1775,6 +1778,52 @@ var import_rstream8 = require("@rstreamlabs/rstream");
1775
1778
  var import_rstream9 = require("@rstreamlabs/rstream");
1776
1779
  var import_rstream10 = require("@rstreamlabs/rstream");
1777
1780
  var import_turn = require("@rstreamlabs/rstream/turn");
1781
+
1782
+ // src/control-plane-fetch.ts
1783
+ function createRequestLifetime(parent, timeoutMilliseconds) {
1784
+ const controller = new AbortController();
1785
+ const abort = () => controller.abort(parent.reason);
1786
+ if (parent.aborted) {
1787
+ abort();
1788
+ } else {
1789
+ parent.addEventListener("abort", abort, { once: true });
1790
+ }
1791
+ const timeout = setTimeout(
1792
+ () => controller.abort(new Error("Control plane request timed out.")),
1793
+ timeoutMilliseconds
1794
+ );
1795
+ return {
1796
+ close: () => {
1797
+ clearTimeout(timeout);
1798
+ parent.removeEventListener("abort", abort);
1799
+ },
1800
+ signal: controller.signal
1801
+ };
1802
+ }
1803
+ function createBoundedControlPlaneFetch(timeoutMilliseconds) {
1804
+ return async (input, init) => {
1805
+ const request = new Request(input, init);
1806
+ const headers = new Headers(request.headers);
1807
+ headers.set("Connection", "close");
1808
+ const lifetime = createRequestLifetime(request.signal, timeoutMilliseconds);
1809
+ try {
1810
+ const response = await globalThis.fetch(request, {
1811
+ headers,
1812
+ signal: lifetime.signal
1813
+ });
1814
+ const responseBody = response.body === null ? null : new Uint8Array(await response.arrayBuffer());
1815
+ return new Response(responseBody, {
1816
+ headers: Object.fromEntries(response.headers.entries()),
1817
+ status: response.status,
1818
+ statusText: response.statusText
1819
+ });
1820
+ } finally {
1821
+ lifetime.close();
1822
+ }
1823
+ };
1824
+ }
1825
+
1826
+ // src/turn.ts
1778
1827
  var z8 = __toESM(require("zod"));
1779
1828
  var import_crypto = __toESM(require("crypto"));
1780
1829
  var defaultTURNPort = 3478;
@@ -1782,6 +1831,8 @@ var defaultTURNSPort = 5349;
1782
1831
  var defaultTURNCredentialTTLSeconds = 10 * 60;
1783
1832
  var maxTURNCredentialTTLSeconds = 60 * 60;
1784
1833
  var maxTURNKeyringBytes = 8 * 1024;
1834
+ var defaultAPIRequestTimeoutMilliseconds = 1e4;
1835
+ var maxAPIRequestTimeoutMilliseconds = 6e4;
1785
1836
  var turnTokenClaimsSchema = z8.object({
1786
1837
  exp: z8.number().int().optional(),
1787
1838
  iat: z8.number().int().optional(),
@@ -1815,6 +1866,15 @@ function normalizeTTLSeconds(ttlSeconds) {
1815
1866
  }
1816
1867
  return ttl;
1817
1868
  }
1869
+ function normalizeAPIRequestTimeoutMilliseconds(timeoutMilliseconds) {
1870
+ const timeout = timeoutMilliseconds ?? defaultAPIRequestTimeoutMilliseconds;
1871
+ if (!Number.isInteger(timeout) || timeout < 1 || timeout > maxAPIRequestTimeoutMilliseconds) {
1872
+ throw new Error(
1873
+ "TURN API request timeout must be an integer between 1 and 60000 milliseconds."
1874
+ );
1875
+ }
1876
+ return timeout;
1877
+ }
1818
1878
  function createAPITURNCredentialsParams(ttlSeconds) {
1819
1879
  return ttlSeconds === void 0 ? {} : { ttlSeconds: normalizeTTLSeconds(ttlSeconds) };
1820
1880
  }
@@ -2008,11 +2068,16 @@ async function createAPITURNCredentials(options) {
2008
2068
  "Project ID or project endpoint is required for TURN API mode."
2009
2069
  );
2010
2070
  }
2071
+ const requestTimeoutMilliseconds = normalizeAPIRequestTimeoutMilliseconds(
2072
+ options.requestTimeoutMilliseconds
2073
+ );
2074
+ const params = createAPITURNCredentialsParams(options.ttlSeconds);
2075
+ const request = options.fetch ?? createBoundedControlPlaneFetch(requestTimeoutMilliseconds);
2011
2076
  const client = new import_rstream10.RstreamClient({
2012
2077
  apiUrl: options.apiUrl,
2013
- credentials: options.credentials
2078
+ credentials: options.credentials,
2079
+ fetch: request
2014
2080
  });
2015
- const params = createAPITURNCredentialsParams(options.ttlSeconds);
2016
2081
  if (projectId) {
2017
2082
  return await client.tunnels.projects.createTurnCredentials(
2018
2083
  projectId,
@@ -2153,8 +2218,10 @@ async function createTURNCredentials(options) {
2153
2218
  return await createAPITURNCredentials({
2154
2219
  apiUrl: options.apiUrl,
2155
2220
  credentials: controlPlaneCredentials,
2221
+ fetch: options.fetch,
2156
2222
  projectEndpoint,
2157
2223
  projectId: options.projectId,
2224
+ requestTimeoutMilliseconds: options.requestTimeoutMilliseconds,
2158
2225
  ttlSeconds: options.ttlSeconds
2159
2226
  });
2160
2227
  }
@@ -2784,6 +2851,11 @@ var webttyEncryptionPolicySchema = z9.enum([
2784
2851
  "explicit_key",
2785
2852
  "workspace_managed"
2786
2853
  ]);
2854
+ var webttyTransportSchema = z9.enum([
2855
+ "plain",
2856
+ "websocket",
2857
+ "webtransport"
2858
+ ]);
2787
2859
  var webttyServerSchema = z9.object({
2788
2860
  tunnel_id: z9.string(),
2789
2861
  tunnel_name: z9.string().optional(),
@@ -2793,7 +2865,11 @@ var webttyServerSchema = z9.object({
2793
2865
  publish: z9.boolean().optional(),
2794
2866
  workspace_id: z9.string().optional(),
2795
2867
  project_id: z9.string().optional(),
2796
- tunnel_protocol: z9.enum(["http", "webtty"]),
2868
+ tunnel_protocol: z9.enum(["http", "webtty"]).optional(),
2869
+ tunnel_type: z9.enum(["bytestream", "datagram"]).optional(),
2870
+ http_version: z9.string().optional(),
2871
+ transport: webttyTransportSchema.optional(),
2872
+ transport_error: z9.string().optional(),
2797
2873
  managed: z9.boolean(),
2798
2874
  host: z9.string(),
2799
2875
  token_auth: z9.boolean(),
@@ -2807,6 +2883,7 @@ var webttyServerSchema = z9.object({
2807
2883
  exec_path: z9.string().optional(),
2808
2884
  fs_path: z9.string().optional(),
2809
2885
  fs_mode: webttyFSModeSchema.optional(),
2886
+ fs_backend: z9.enum(["webdav", "webrtc"]).optional(),
2810
2887
  os_family: osFamilySchema.optional(),
2811
2888
  arch: z9.string().optional(),
2812
2889
  os_id: z9.string().optional(),
@@ -2833,11 +2910,12 @@ function webTTYCapabilitiesForLabels(labels) {
2833
2910
  function webTTYHasCapability(capabilities, capability) {
2834
2911
  return capabilities.includes(capability);
2835
2912
  }
2836
- function parser(tunnel) {
2913
+ function parser(tunnel, includePrivate) {
2837
2914
  if (tunnel.status !== "online") return null;
2838
- if (tunnel.publish !== true) return null;
2915
+ if (tunnel.publish !== true && !includePrivate) return null;
2839
2916
  const managedProtocol = tunnel.protocol === "webtty";
2840
- if (tunnel.protocol !== "http" && !managedProtocol) return null;
2917
+ if (tunnel.protocol !== void 0 && tunnel.protocol !== "http" && !managedProtocol)
2918
+ return null;
2841
2919
  const tunnelLabels = tunnel.labels ?? {};
2842
2920
  if (!managedProtocol && tunnelLabels["application-protocol"] !== "rstream.webtty") {
2843
2921
  return null;
@@ -2865,8 +2943,11 @@ function parser(tunnel) {
2865
2943
  workspace_id: tunnel.workspace_id,
2866
2944
  project_id: tunnel.project_id,
2867
2945
  tunnel_protocol: tunnel.protocol,
2946
+ tunnel_type: tunnel.type,
2947
+ http_version: tunnel.http_version,
2948
+ ...webTTYTransportMetadata(tunnelLabels["rstream.webtty.transport"]),
2868
2949
  managed: managedProtocol,
2869
- host: formatTunnelHost(tunnel),
2950
+ host: formatTunnelHost(tunnel) ?? "",
2870
2951
  token_auth: tunnel.token_auth === true,
2871
2952
  server_id: serverId || void 0,
2872
2953
  server_name: serverName || void 0,
@@ -2877,6 +2958,7 @@ function parser(tunnel) {
2877
2958
  capabilities,
2878
2959
  exec_path: webTTYHasCapability(capabilities, "exec") ? tunnelLabels["rstream.webtty.exec.path"] ?? "/" : void 0,
2879
2960
  fs_path: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.path"] ?? "/fs" : void 0,
2961
+ fs_backend: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.backend"] ?? "webdav" : void 0,
2880
2962
  fs_mode: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.mode"] ?? "read-write" : void 0,
2881
2963
  os_family: tunnelLabels["rstream.webtty.os_family"],
2882
2964
  arch: tunnelLabels["rstream.webtty.arch"],
@@ -2892,8 +2974,54 @@ function parser(tunnel) {
2892
2974
  if (!parsed.success) return null;
2893
2975
  return parsed.data;
2894
2976
  }
2895
- function parseWebTTYServers(tunnels) {
2896
- return tunnels.map((tunnel) => parser(tunnel)).filter((server) => server !== null);
2977
+ function parseWebTTYServers(tunnels, options = {}) {
2978
+ return tunnels.map((tunnel) => parser(tunnel, options.includePrivate === true)).filter((server) => server !== null);
2979
+ }
2980
+ function webTTYTransportMetadata(value) {
2981
+ if (value === void 0) return {};
2982
+ const parsed = webttyTransportSchema.safeParse(value);
2983
+ if (!parsed.success)
2984
+ return {
2985
+ transport_error: "Server advertises an invalid WebTTY transport."
2986
+ };
2987
+ return { transport: parsed.data };
2988
+ }
2989
+ function resolveWebTTYServerTransport(server, requested) {
2990
+ if (server.transport_error !== void 0)
2991
+ throw new Error(server.transport_error);
2992
+ const advertised = server.transport ?? (server.tunnel_type === "datagram" ? "webtransport" : void 0);
2993
+ if (advertised !== void 0 && server.tunnel_type !== void 0 && advertised === "webtransport" !== (server.tunnel_type === "datagram")) {
2994
+ throw new Error(
2995
+ `Server WebTTY transport ${advertised} conflicts with tunnel type ${server.tunnel_type}.`
2996
+ );
2997
+ }
2998
+ if (advertised === "webtransport" && server.http_version !== void 0 && server.http_version !== "h3") {
2999
+ throw new Error("Server WebTransport requires HTTP/3.");
3000
+ }
3001
+ if (requested !== void 0 && advertised !== void 0 && requested !== advertised) {
3002
+ throw new Error(
3003
+ `Requested WebTTY transport ${requested} conflicts with server transport ${advertised}.`
3004
+ );
3005
+ }
3006
+ return advertised ?? requested ?? "websocket";
3007
+ }
3008
+ function resolveWebTTYBrowserEndpoint(server) {
3009
+ const transport = resolveWebTTYServerTransport(server);
3010
+ if (transport === "plain")
3011
+ throw new Error(
3012
+ "Plain WebTTY requires a native client; this server cannot be opened in a browser."
3013
+ );
3014
+ if (server.publish === false || server.host === "")
3015
+ throw new Error(
3016
+ "Private WebTTY requires a native client and its rstrm:// address."
3017
+ );
3018
+ const url = new URL(
3019
+ `${transport === "webtransport" ? "https" : "wss"}://${server.host}`
3020
+ );
3021
+ const execPath = server.exec_path?.trim();
3022
+ if (execPath)
3023
+ url.pathname = execPath.startsWith("/") ? execPath : `/${execPath}`;
3024
+ return { url: url.toString(), transport };
2897
3025
  }
2898
3026
  // Annotate the CommonJS export names for ESM import in node:
2899
3027
  0 && (module.exports = {
@@ -2951,7 +3079,9 @@ function parseWebTTYServers(tunnels) {
2951
3079
  resolveTunnelsCredentials,
2952
3080
  resolveTunnelsEngine,
2953
3081
  resolveTunnelsRegion,
3082
+ resolveWebTTYBrowserEndpoint,
2954
3083
  resolveWebTTYControlRequestParamsSchema,
3084
+ resolveWebTTYServerTransport,
2955
3085
  signWebhookPayload,
2956
3086
  streamEndpointSchema,
2957
3087
  streamEntrySchema,
@@ -2981,5 +3111,6 @@ function parseWebTTYServers(tunnels) {
2981
3111
  webTTYTransportSchema,
2982
3112
  webhookEventsSchema,
2983
3113
  webttyServerSchema,
3114
+ webttyTransportSchema,
2984
3115
  wsEventsSchema
2985
3116
  });
package/dist/index.mjs CHANGED
@@ -412,6 +412,52 @@ import { isClientCredentials as isClientCredentials2 } from "@rstreamlabs/rstrea
412
412
  import { isTokenCredentials as isTokenCredentials2 } from "@rstreamlabs/rstream";
413
413
  import { RstreamClient as RstreamClient2 } from "@rstreamlabs/rstream";
414
414
  import { turnCredentialsSchema } from "@rstreamlabs/rstream/turn";
415
+
416
+ // src/control-plane-fetch.ts
417
+ function createRequestLifetime(parent, timeoutMilliseconds) {
418
+ const controller = new AbortController();
419
+ const abort = () => controller.abort(parent.reason);
420
+ if (parent.aborted) {
421
+ abort();
422
+ } else {
423
+ parent.addEventListener("abort", abort, { once: true });
424
+ }
425
+ const timeout = setTimeout(
426
+ () => controller.abort(new Error("Control plane request timed out.")),
427
+ timeoutMilliseconds
428
+ );
429
+ return {
430
+ close: () => {
431
+ clearTimeout(timeout);
432
+ parent.removeEventListener("abort", abort);
433
+ },
434
+ signal: controller.signal
435
+ };
436
+ }
437
+ function createBoundedControlPlaneFetch(timeoutMilliseconds) {
438
+ return async (input, init) => {
439
+ const request = new Request(input, init);
440
+ const headers = new Headers(request.headers);
441
+ headers.set("Connection", "close");
442
+ const lifetime = createRequestLifetime(request.signal, timeoutMilliseconds);
443
+ try {
444
+ const response = await globalThis.fetch(request, {
445
+ headers,
446
+ signal: lifetime.signal
447
+ });
448
+ const responseBody = response.body === null ? null : new Uint8Array(await response.arrayBuffer());
449
+ return new Response(responseBody, {
450
+ headers: Object.fromEntries(response.headers.entries()),
451
+ status: response.status,
452
+ statusText: response.statusText
453
+ });
454
+ } finally {
455
+ lifetime.close();
456
+ }
457
+ };
458
+ }
459
+
460
+ // src/turn.ts
415
461
  import * as z from "zod";
416
462
  import crypto from "crypto";
417
463
  var defaultTURNPort = 3478;
@@ -419,6 +465,8 @@ var defaultTURNSPort = 5349;
419
465
  var defaultTURNCredentialTTLSeconds = 10 * 60;
420
466
  var maxTURNCredentialTTLSeconds = 60 * 60;
421
467
  var maxTURNKeyringBytes = 8 * 1024;
468
+ var defaultAPIRequestTimeoutMilliseconds = 1e4;
469
+ var maxAPIRequestTimeoutMilliseconds = 6e4;
422
470
  var turnTokenClaimsSchema = z.object({
423
471
  exp: z.number().int().optional(),
424
472
  iat: z.number().int().optional(),
@@ -452,6 +500,15 @@ function normalizeTTLSeconds(ttlSeconds) {
452
500
  }
453
501
  return ttl;
454
502
  }
503
+ function normalizeAPIRequestTimeoutMilliseconds(timeoutMilliseconds) {
504
+ const timeout = timeoutMilliseconds ?? defaultAPIRequestTimeoutMilliseconds;
505
+ if (!Number.isInteger(timeout) || timeout < 1 || timeout > maxAPIRequestTimeoutMilliseconds) {
506
+ throw new Error(
507
+ "TURN API request timeout must be an integer between 1 and 60000 milliseconds."
508
+ );
509
+ }
510
+ return timeout;
511
+ }
455
512
  function createAPITURNCredentialsParams(ttlSeconds) {
456
513
  return ttlSeconds === void 0 ? {} : { ttlSeconds: normalizeTTLSeconds(ttlSeconds) };
457
514
  }
@@ -645,11 +702,16 @@ async function createAPITURNCredentials(options) {
645
702
  "Project ID or project endpoint is required for TURN API mode."
646
703
  );
647
704
  }
705
+ const requestTimeoutMilliseconds = normalizeAPIRequestTimeoutMilliseconds(
706
+ options.requestTimeoutMilliseconds
707
+ );
708
+ const params = createAPITURNCredentialsParams(options.ttlSeconds);
709
+ const request = options.fetch ?? createBoundedControlPlaneFetch(requestTimeoutMilliseconds);
648
710
  const client = new RstreamClient2({
649
711
  apiUrl: options.apiUrl,
650
- credentials: options.credentials
712
+ credentials: options.credentials,
713
+ fetch: request
651
714
  });
652
- const params = createAPITURNCredentialsParams(options.ttlSeconds);
653
715
  if (projectId) {
654
716
  return await client.tunnels.projects.createTurnCredentials(
655
717
  projectId,
@@ -790,8 +852,10 @@ async function createTURNCredentials(options) {
790
852
  return await createAPITURNCredentials({
791
853
  apiUrl: options.apiUrl,
792
854
  credentials: controlPlaneCredentials,
855
+ fetch: options.fetch,
793
856
  projectEndpoint,
794
857
  projectId: options.projectId,
858
+ requestTimeoutMilliseconds: options.requestTimeoutMilliseconds,
795
859
  ttlSeconds: options.ttlSeconds
796
860
  });
797
861
  }
@@ -1421,6 +1485,11 @@ var webttyEncryptionPolicySchema = z2.enum([
1421
1485
  "explicit_key",
1422
1486
  "workspace_managed"
1423
1487
  ]);
1488
+ var webttyTransportSchema = z2.enum([
1489
+ "plain",
1490
+ "websocket",
1491
+ "webtransport"
1492
+ ]);
1424
1493
  var webttyServerSchema = z2.object({
1425
1494
  tunnel_id: z2.string(),
1426
1495
  tunnel_name: z2.string().optional(),
@@ -1430,7 +1499,11 @@ var webttyServerSchema = z2.object({
1430
1499
  publish: z2.boolean().optional(),
1431
1500
  workspace_id: z2.string().optional(),
1432
1501
  project_id: z2.string().optional(),
1433
- tunnel_protocol: z2.enum(["http", "webtty"]),
1502
+ tunnel_protocol: z2.enum(["http", "webtty"]).optional(),
1503
+ tunnel_type: z2.enum(["bytestream", "datagram"]).optional(),
1504
+ http_version: z2.string().optional(),
1505
+ transport: webttyTransportSchema.optional(),
1506
+ transport_error: z2.string().optional(),
1434
1507
  managed: z2.boolean(),
1435
1508
  host: z2.string(),
1436
1509
  token_auth: z2.boolean(),
@@ -1444,6 +1517,7 @@ var webttyServerSchema = z2.object({
1444
1517
  exec_path: z2.string().optional(),
1445
1518
  fs_path: z2.string().optional(),
1446
1519
  fs_mode: webttyFSModeSchema.optional(),
1520
+ fs_backend: z2.enum(["webdav", "webrtc"]).optional(),
1447
1521
  os_family: osFamilySchema.optional(),
1448
1522
  arch: z2.string().optional(),
1449
1523
  os_id: z2.string().optional(),
@@ -1470,11 +1544,12 @@ function webTTYCapabilitiesForLabels(labels) {
1470
1544
  function webTTYHasCapability(capabilities, capability) {
1471
1545
  return capabilities.includes(capability);
1472
1546
  }
1473
- function parser(tunnel) {
1547
+ function parser(tunnel, includePrivate) {
1474
1548
  if (tunnel.status !== "online") return null;
1475
- if (tunnel.publish !== true) return null;
1549
+ if (tunnel.publish !== true && !includePrivate) return null;
1476
1550
  const managedProtocol = tunnel.protocol === "webtty";
1477
- if (tunnel.protocol !== "http" && !managedProtocol) return null;
1551
+ if (tunnel.protocol !== void 0 && tunnel.protocol !== "http" && !managedProtocol)
1552
+ return null;
1478
1553
  const tunnelLabels = tunnel.labels ?? {};
1479
1554
  if (!managedProtocol && tunnelLabels["application-protocol"] !== "rstream.webtty") {
1480
1555
  return null;
@@ -1502,8 +1577,11 @@ function parser(tunnel) {
1502
1577
  workspace_id: tunnel.workspace_id,
1503
1578
  project_id: tunnel.project_id,
1504
1579
  tunnel_protocol: tunnel.protocol,
1580
+ tunnel_type: tunnel.type,
1581
+ http_version: tunnel.http_version,
1582
+ ...webTTYTransportMetadata(tunnelLabels["rstream.webtty.transport"]),
1505
1583
  managed: managedProtocol,
1506
- host: formatTunnelHost(tunnel),
1584
+ host: formatTunnelHost(tunnel) ?? "",
1507
1585
  token_auth: tunnel.token_auth === true,
1508
1586
  server_id: serverId || void 0,
1509
1587
  server_name: serverName || void 0,
@@ -1514,6 +1592,7 @@ function parser(tunnel) {
1514
1592
  capabilities,
1515
1593
  exec_path: webTTYHasCapability(capabilities, "exec") ? tunnelLabels["rstream.webtty.exec.path"] ?? "/" : void 0,
1516
1594
  fs_path: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.path"] ?? "/fs" : void 0,
1595
+ fs_backend: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.backend"] ?? "webdav" : void 0,
1517
1596
  fs_mode: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.mode"] ?? "read-write" : void 0,
1518
1597
  os_family: tunnelLabels["rstream.webtty.os_family"],
1519
1598
  arch: tunnelLabels["rstream.webtty.arch"],
@@ -1529,8 +1608,54 @@ function parser(tunnel) {
1529
1608
  if (!parsed.success) return null;
1530
1609
  return parsed.data;
1531
1610
  }
1532
- function parseWebTTYServers(tunnels) {
1533
- return tunnels.map((tunnel) => parser(tunnel)).filter((server) => server !== null);
1611
+ function parseWebTTYServers(tunnels, options = {}) {
1612
+ return tunnels.map((tunnel) => parser(tunnel, options.includePrivate === true)).filter((server) => server !== null);
1613
+ }
1614
+ function webTTYTransportMetadata(value) {
1615
+ if (value === void 0) return {};
1616
+ const parsed = webttyTransportSchema.safeParse(value);
1617
+ if (!parsed.success)
1618
+ return {
1619
+ transport_error: "Server advertises an invalid WebTTY transport."
1620
+ };
1621
+ return { transport: parsed.data };
1622
+ }
1623
+ function resolveWebTTYServerTransport(server, requested) {
1624
+ if (server.transport_error !== void 0)
1625
+ throw new Error(server.transport_error);
1626
+ const advertised = server.transport ?? (server.tunnel_type === "datagram" ? "webtransport" : void 0);
1627
+ if (advertised !== void 0 && server.tunnel_type !== void 0 && advertised === "webtransport" !== (server.tunnel_type === "datagram")) {
1628
+ throw new Error(
1629
+ `Server WebTTY transport ${advertised} conflicts with tunnel type ${server.tunnel_type}.`
1630
+ );
1631
+ }
1632
+ if (advertised === "webtransport" && server.http_version !== void 0 && server.http_version !== "h3") {
1633
+ throw new Error("Server WebTransport requires HTTP/3.");
1634
+ }
1635
+ if (requested !== void 0 && advertised !== void 0 && requested !== advertised) {
1636
+ throw new Error(
1637
+ `Requested WebTTY transport ${requested} conflicts with server transport ${advertised}.`
1638
+ );
1639
+ }
1640
+ return advertised ?? requested ?? "websocket";
1641
+ }
1642
+ function resolveWebTTYBrowserEndpoint(server) {
1643
+ const transport = resolveWebTTYServerTransport(server);
1644
+ if (transport === "plain")
1645
+ throw new Error(
1646
+ "Plain WebTTY requires a native client; this server cannot be opened in a browser."
1647
+ );
1648
+ if (server.publish === false || server.host === "")
1649
+ throw new Error(
1650
+ "Private WebTTY requires a native client and its rstrm:// address."
1651
+ );
1652
+ const url = new URL(
1653
+ `${transport === "webtransport" ? "https" : "wss"}://${server.host}`
1654
+ );
1655
+ const execPath = server.exec_path?.trim();
1656
+ if (execPath)
1657
+ url.pathname = execPath.startsWith("/") ? execPath : `/${execPath}`;
1658
+ return { url: url.toString(), transport };
1534
1659
  }
1535
1660
  export {
1536
1661
  RstreamAuthResource,
@@ -1587,7 +1712,9 @@ export {
1587
1712
  resolveTunnelsCredentials,
1588
1713
  resolveTunnelsEngine,
1589
1714
  resolveTunnelsRegion,
1715
+ resolveWebTTYBrowserEndpoint,
1590
1716
  resolveWebTTYControlRequestParamsSchema,
1717
+ resolveWebTTYServerTransport,
1591
1718
  signWebhookPayload,
1592
1719
  streamEndpointSchema,
1593
1720
  streamEntrySchema,
@@ -1617,5 +1744,6 @@ export {
1617
1744
  webTTYTransportSchema,
1618
1745
  webhookEventsSchema,
1619
1746
  webttyServerSchema,
1747
+ webttyTransportSchema,
1620
1748
  wsEventsSchema
1621
1749
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rstreamlabs/tunnels",
3
- "version": "3.8.2",
3
+ "version": "3.9.0",
4
4
  "description": "JS/TS SDK for the rstream Engine API and tunnel runtime APIs.",
5
5
  "author": "@uartnet <hello@rstream.io>",
6
6
  "repository": {
@@ -30,15 +30,15 @@
30
30
  ],
31
31
  "scripts": {
32
32
  "build:watch": "tsup --watch",
33
- "build": "tsup",
34
- "clean": "rm -rf dist",
33
+ "build": "tsc --emitDeclarationOnly --declarationMap false --rootDir src --outDir .generated/types && tsup",
34
+ "clean": "rm -rf dist .generated/types",
35
35
  "lint": "eslint \"./**/*.ts*\"",
36
36
  "pretest": "npm --prefix ../rstream run build && npm run build",
37
37
  "test": "node --test ./test/**/*.test.cjs",
38
38
  "type-check": "tsc --noEmit"
39
39
  },
40
40
  "devDependencies": {
41
- "@turbo/gen": "^2.10.6",
41
+ "@turbo/gen": "^2.10.11",
42
42
  "@types/node": "^26",
43
43
  "eslint": "9.39.5",
44
44
  "eslint-config": "*",
@@ -47,7 +47,7 @@
47
47
  "typescript-config": "*"
48
48
  },
49
49
  "dependencies": {
50
- "@rstreamlabs/rstream": "3.8.1",
50
+ "@rstreamlabs/rstream": "3.8.2",
51
51
  "@types/jsonwebtoken": "^9.0.10",
52
52
  "jsonwebtoken": "^9.0.3",
53
53
  "zod": "^4.4.3"