@rstreamlabs/tunnels 3.8.3 → 3.10.1
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 +54 -0
- package/dist/{chunk-3VBF43NB.mjs → chunk-DQN5T7CI.mjs} +207 -9
- package/dist/index.d.mts +321 -256
- package/dist/index.d.ts +321 -256
- package/dist/index.js +1113 -157
- package/dist/index.mjs +778 -142
- package/dist/node-engine-http-6DJZZEME.mjs +102 -0
- package/dist/openapi.d.mts +260 -10
- package/dist/openapi.d.ts +260 -10
- package/dist/openapi.js +207 -9
- package/dist/openapi.mjs +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -5,6 +5,14 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res, err) => function __init() {
|
|
9
|
+
if (err) throw err[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err = [e], e;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
8
16
|
var __export = (target, all) => {
|
|
9
17
|
for (var name in all)
|
|
10
18
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,6 +35,116 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
35
|
));
|
|
28
36
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
37
|
|
|
38
|
+
// src/node-engine-http.ts
|
|
39
|
+
var node_engine_http_exports = {};
|
|
40
|
+
__export(node_engine_http_exports, {
|
|
41
|
+
NodeEngineHTTP: () => NodeEngineHTTP
|
|
42
|
+
});
|
|
43
|
+
var import_node_https, import_node_https2, responseHeaders, responseBody, NodeEngineHTTP;
|
|
44
|
+
var init_node_engine_http = __esm({
|
|
45
|
+
"src/node-engine-http.ts"() {
|
|
46
|
+
"use strict";
|
|
47
|
+
import_node_https = require("https");
|
|
48
|
+
import_node_https2 = require("https");
|
|
49
|
+
responseHeaders = (response) => {
|
|
50
|
+
const headers = new Headers();
|
|
51
|
+
for (const [name, value] of Object.entries(response.headers)) {
|
|
52
|
+
if (Array.isArray(value))
|
|
53
|
+
for (const item of value) headers.append(name, item);
|
|
54
|
+
else if (value !== void 0) headers.set(name, value);
|
|
55
|
+
}
|
|
56
|
+
return headers;
|
|
57
|
+
};
|
|
58
|
+
responseBody = (response) => {
|
|
59
|
+
const state = { ended: false };
|
|
60
|
+
return new ReadableStream({
|
|
61
|
+
start(controller) {
|
|
62
|
+
response.on("data", (chunk) => {
|
|
63
|
+
controller.enqueue(new Uint8Array(chunk));
|
|
64
|
+
response.pause();
|
|
65
|
+
});
|
|
66
|
+
response.once("end", () => {
|
|
67
|
+
if (!state.ended) {
|
|
68
|
+
state.ended = true;
|
|
69
|
+
controller.close();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
response.once("error", (error) => {
|
|
73
|
+
if (!state.ended) {
|
|
74
|
+
state.ended = true;
|
|
75
|
+
controller.error(error);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
response.once("aborted", () => {
|
|
79
|
+
if (!state.ended) {
|
|
80
|
+
state.ended = true;
|
|
81
|
+
controller.error(new Error("Engine response was interrupted."));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
pull() {
|
|
86
|
+
response.resume();
|
|
87
|
+
},
|
|
88
|
+
cancel() {
|
|
89
|
+
state.ended = true;
|
|
90
|
+
response.destroy();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
NodeEngineHTTP = class {
|
|
95
|
+
constructor(tls) {
|
|
96
|
+
this.tls = tls;
|
|
97
|
+
}
|
|
98
|
+
tls;
|
|
99
|
+
agent = new import_node_https.Agent({
|
|
100
|
+
keepAlive: true,
|
|
101
|
+
maxSockets: 16,
|
|
102
|
+
maxFreeSockets: 4,
|
|
103
|
+
maxTotalSockets: 16
|
|
104
|
+
});
|
|
105
|
+
fetch = async (input, init) => {
|
|
106
|
+
const incoming = new Request(input, init);
|
|
107
|
+
const url = new URL(incoming.url);
|
|
108
|
+
if (url.protocol !== "https:" || url.username || url.password)
|
|
109
|
+
throw new Error(
|
|
110
|
+
"Engine HTTP requests require HTTPS without URL credentials."
|
|
111
|
+
);
|
|
112
|
+
const body = incoming.body ? new Uint8Array(await incoming.arrayBuffer()) : void 0;
|
|
113
|
+
incoming.signal.throwIfAborted();
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const outgoing = (0, import_node_https2.request)(
|
|
116
|
+
url,
|
|
117
|
+
{
|
|
118
|
+
...this.tls,
|
|
119
|
+
servername: url.hostname,
|
|
120
|
+
agent: this.agent,
|
|
121
|
+
method: incoming.method,
|
|
122
|
+
headers: Object.fromEntries(incoming.headers),
|
|
123
|
+
signal: incoming.signal
|
|
124
|
+
},
|
|
125
|
+
(response) => {
|
|
126
|
+
const status = response.statusCode ?? 502;
|
|
127
|
+
const empty = incoming.method === "HEAD" || [204, 205, 304].includes(status);
|
|
128
|
+
if (empty) response.resume();
|
|
129
|
+
resolve(
|
|
130
|
+
new Response(empty ? null : responseBody(response), {
|
|
131
|
+
status,
|
|
132
|
+
headers: responseHeaders(response)
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
outgoing.once("error", reject);
|
|
138
|
+
outgoing.end(body);
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
close() {
|
|
142
|
+
this.agent.destroy();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
30
148
|
// src/index.ts
|
|
31
149
|
var index_exports = {};
|
|
32
150
|
__export(index_exports, {
|
|
@@ -84,7 +202,9 @@ __export(index_exports, {
|
|
|
84
202
|
resolveTunnelsCredentials: () => resolveTunnelsCredentials,
|
|
85
203
|
resolveTunnelsEngine: () => resolveTunnelsEngine,
|
|
86
204
|
resolveTunnelsRegion: () => resolveTunnelsRegion,
|
|
205
|
+
resolveWebTTYBrowserEndpoint: () => resolveWebTTYBrowserEndpoint,
|
|
87
206
|
resolveWebTTYControlRequestParamsSchema: () => resolveWebTTYControlRequestParamsSchema,
|
|
207
|
+
resolveWebTTYServerTransport: () => resolveWebTTYServerTransport,
|
|
88
208
|
signWebhookPayload: () => signWebhookPayload,
|
|
89
209
|
streamEndpointSchema: () => streamEndpointSchema,
|
|
90
210
|
streamEntrySchema: () => streamEntrySchema,
|
|
@@ -94,6 +214,7 @@ __export(index_exports, {
|
|
|
94
214
|
streamWebTTYMetadataSchema: () => streamWebTTYMetadataSchema,
|
|
95
215
|
tunnelFilterSchema: () => tunnelFilterSchema,
|
|
96
216
|
turnCredentialModeSchema: () => turnCredentialModeSchema,
|
|
217
|
+
validateTURNCredentialsRequest: () => validateTURNCredentialsRequest,
|
|
97
218
|
webTTYCapabilitiesSchema: () => webTTYCapabilitiesSchema,
|
|
98
219
|
webTTYContextMetadataSchema: () => webTTYContextMetadataSchema,
|
|
99
220
|
webTTYControlRequestSchema: () => webTTYControlRequestSchema,
|
|
@@ -114,6 +235,7 @@ __export(index_exports, {
|
|
|
114
235
|
webTTYTransportSchema: () => webTTYTransportSchema,
|
|
115
236
|
webhookEventsSchema: () => webhookEventsSchema,
|
|
116
237
|
webttyServerSchema: () => webttyServerSchema,
|
|
238
|
+
webttyTransportSchema: () => webttyTransportSchema,
|
|
117
239
|
wsEventsSchema: () => wsEventsSchema
|
|
118
240
|
});
|
|
119
241
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -1298,6 +1420,19 @@ var watchParamsSchema = z7.object({
|
|
|
1298
1420
|
tunnels: tunnelFilterSchema.optional()
|
|
1299
1421
|
});
|
|
1300
1422
|
var enginePermissions = {
|
|
1423
|
+
"turn.credentials.create": {
|
|
1424
|
+
name: "TURN Credentials: Create",
|
|
1425
|
+
description: "Issue short-lived TURN credentials through either the Control plane or Engine API.",
|
|
1426
|
+
rules: [
|
|
1427
|
+
{
|
|
1428
|
+
resource: [
|
|
1429
|
+
"api.turn-server.credentials",
|
|
1430
|
+
"engine.turn-server.credentials"
|
|
1431
|
+
],
|
|
1432
|
+
action: "create"
|
|
1433
|
+
}
|
|
1434
|
+
]
|
|
1435
|
+
},
|
|
1301
1436
|
"tunnels.resources.read-only": {
|
|
1302
1437
|
name: "Discovery: Read-Only",
|
|
1303
1438
|
description: "Read active clients, active tunnels, and live state events.",
|
|
@@ -1377,13 +1512,13 @@ var engineOpenApiDocument = {
|
|
|
1377
1512
|
}
|
|
1378
1513
|
}
|
|
1379
1514
|
],
|
|
1380
|
-
security: [
|
|
1381
|
-
{
|
|
1382
|
-
bearerAuth: []
|
|
1383
|
-
}
|
|
1384
|
-
],
|
|
1515
|
+
security: [{ bearerAuth: [] }, { mutualTLS: [] }],
|
|
1385
1516
|
components: {
|
|
1386
1517
|
securitySchemes: {
|
|
1518
|
+
mutualTLS: {
|
|
1519
|
+
type: "mutualTLS",
|
|
1520
|
+
description: "EE project API only, on the dedicated authority returned by anonymous HTTPS discovery. Present one registered client certificate without a bearer or query token. Ordinary browser-facing hosts never request a client certificate."
|
|
1521
|
+
},
|
|
1387
1522
|
bearerAuth: {
|
|
1388
1523
|
type: "http",
|
|
1389
1524
|
scheme: "bearer",
|
|
@@ -1451,6 +1586,64 @@ var engineOpenApiDocument = {
|
|
|
1451
1586
|
Forbidden: errorResponse("The token is not authorized.")
|
|
1452
1587
|
},
|
|
1453
1588
|
schemas: {
|
|
1589
|
+
EngineDiscovery: {
|
|
1590
|
+
type: "object",
|
|
1591
|
+
required: ["version", "projectEndpoint", "mtlsApiUrl", "capabilities"],
|
|
1592
|
+
properties: {
|
|
1593
|
+
version: { type: "integer", const: 1 },
|
|
1594
|
+
projectEndpoint: { type: "string" },
|
|
1595
|
+
mtlsApiUrl: {
|
|
1596
|
+
type: "string",
|
|
1597
|
+
format: "uri",
|
|
1598
|
+
example: "https://project.mtls.engine.example/api"
|
|
1599
|
+
},
|
|
1600
|
+
capabilities: {
|
|
1601
|
+
type: "array",
|
|
1602
|
+
items: { type: "string" },
|
|
1603
|
+
examples: [["engine-api-mtls", "turn-credentials-v2"]]
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
},
|
|
1607
|
+
CreateTURNCredentials: {
|
|
1608
|
+
type: "object",
|
|
1609
|
+
additionalProperties: false,
|
|
1610
|
+
properties: {
|
|
1611
|
+
ttlSeconds: {
|
|
1612
|
+
type: "integer",
|
|
1613
|
+
minimum: 1,
|
|
1614
|
+
maximum: 3600,
|
|
1615
|
+
default: 600
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
},
|
|
1619
|
+
TURNCredentials: {
|
|
1620
|
+
type: "object",
|
|
1621
|
+
required: ["username", "credential", "urls", "ttl"],
|
|
1622
|
+
properties: {
|
|
1623
|
+
username: {
|
|
1624
|
+
type: "string",
|
|
1625
|
+
maxLength: 512,
|
|
1626
|
+
description: "Opaque short-lived v2 issued identity. Do not parse in clients."
|
|
1627
|
+
},
|
|
1628
|
+
credential: {
|
|
1629
|
+
type: "string",
|
|
1630
|
+
maxLength: 1024,
|
|
1631
|
+
description: "Short-lived TURN password."
|
|
1632
|
+
},
|
|
1633
|
+
urls: {
|
|
1634
|
+
type: "array",
|
|
1635
|
+
minItems: 1,
|
|
1636
|
+
maxItems: 16,
|
|
1637
|
+
items: { type: "string", maxLength: 2048 }
|
|
1638
|
+
},
|
|
1639
|
+
ttl: {
|
|
1640
|
+
type: "integer",
|
|
1641
|
+
minimum: 1,
|
|
1642
|
+
maximum: 3600,
|
|
1643
|
+
description: "Effective remaining lifetime in seconds, clipped to identity, source credential and certificate expiry."
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
},
|
|
1454
1647
|
Client: jsonSchemaFor(clientSchema),
|
|
1455
1648
|
Event: jsonSchemaFor(wsEventsSchema),
|
|
1456
1649
|
ListClientsParams: jsonSchemaFor(listClientsParamsSchema),
|
|
@@ -1485,6 +1678,133 @@ var engineOpenApiDocument = {
|
|
|
1485
1678
|
"read-managed-webtty-recordings": ["webtty.logs.read-only"]
|
|
1486
1679
|
},
|
|
1487
1680
|
paths: {
|
|
1681
|
+
"/.well-known/rstream/engine": {
|
|
1682
|
+
get: {
|
|
1683
|
+
tags: ["Discovery"],
|
|
1684
|
+
summary: "Discover the dedicated project mTLS API.",
|
|
1685
|
+
operationId: "get_engine_discovery",
|
|
1686
|
+
description: "Anonymous GET on the ordinary project Engine authority. Follow the returned HTTPS URL; do not infer an mTLS hostname. Cache per client using Cache-Control (normally one hour). No certificate, bearer token, cookie or redirect is required. Available only where an explicit EE mapping is configured.",
|
|
1687
|
+
security: [],
|
|
1688
|
+
responses: {
|
|
1689
|
+
"200": {
|
|
1690
|
+
description: "Project-bound discovery. Capabilities describe enabled server features; callers still need the corresponding rights.",
|
|
1691
|
+
headers: {
|
|
1692
|
+
"Cache-Control": {
|
|
1693
|
+
schema: { type: "string" },
|
|
1694
|
+
example: "public, max-age=3600"
|
|
1695
|
+
}
|
|
1696
|
+
},
|
|
1697
|
+
content: {
|
|
1698
|
+
"application/json": {
|
|
1699
|
+
schema: { $ref: "#/components/schemas/EngineDiscovery" }
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
},
|
|
1703
|
+
"404": {
|
|
1704
|
+
description: "Dedicated mTLS API is not configured on this authority."
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
},
|
|
1709
|
+
"/api/turn-server/credentials": {
|
|
1710
|
+
post: {
|
|
1711
|
+
tags: ["TURN"],
|
|
1712
|
+
summary: "Issue short-lived project TURN credentials.",
|
|
1713
|
+
operationId: "create_engine_turn_credentials",
|
|
1714
|
+
description: "EE managed projects. Same request and response contract as Control plane project TURN issuance. Empty body is valid. Issued credentials require turn.credentials.create only; turn.relay.allocate applies to local PAT/APP derivation. Issuance does not allocate a relay and does not bypass relay quotas or revocation checks.",
|
|
1715
|
+
security: [{ bearerAuth: [] }, { mutualTLS: [] }],
|
|
1716
|
+
requestBody: {
|
|
1717
|
+
required: false,
|
|
1718
|
+
content: {
|
|
1719
|
+
"application/json": {
|
|
1720
|
+
schema: { $ref: "#/components/schemas/CreateTURNCredentials" }
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
},
|
|
1724
|
+
responses: {
|
|
1725
|
+
"200": {
|
|
1726
|
+
description: "Short-lived project TURN credentials.",
|
|
1727
|
+
headers: {
|
|
1728
|
+
"Cache-Control": {
|
|
1729
|
+
schema: { type: "string" },
|
|
1730
|
+
example: "no-store"
|
|
1731
|
+
}
|
|
1732
|
+
},
|
|
1733
|
+
content: {
|
|
1734
|
+
"application/json": {
|
|
1735
|
+
schema: { $ref: "#/components/schemas/TURNCredentials" }
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
},
|
|
1739
|
+
"400": {
|
|
1740
|
+
description: "Malformed, unknown or out-of-range request fields; body exceeds 4 KiB.",
|
|
1741
|
+
headers: {
|
|
1742
|
+
"Cache-Control": {
|
|
1743
|
+
schema: { type: "string" },
|
|
1744
|
+
example: "no-store"
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
},
|
|
1748
|
+
"401": {
|
|
1749
|
+
description: "Missing, invalid, expired or mixed authentication proofs.",
|
|
1750
|
+
headers: {
|
|
1751
|
+
"Cache-Control": {
|
|
1752
|
+
schema: { type: "string" },
|
|
1753
|
+
example: "no-store"
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
},
|
|
1757
|
+
"403": {
|
|
1758
|
+
description: "Missing turn.credentials.create permission, workspace membership or project resource scope.",
|
|
1759
|
+
headers: {
|
|
1760
|
+
"Cache-Control": {
|
|
1761
|
+
schema: { type: "string" },
|
|
1762
|
+
example: "no-store"
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
},
|
|
1766
|
+
"404": {
|
|
1767
|
+
description: "Project not found.",
|
|
1768
|
+
headers: {
|
|
1769
|
+
"Cache-Control": {
|
|
1770
|
+
schema: { type: "string" },
|
|
1771
|
+
example: "no-store"
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
},
|
|
1775
|
+
"409": {
|
|
1776
|
+
description: "Project or serving infrastructure is inactive.",
|
|
1777
|
+
headers: {
|
|
1778
|
+
"Cache-Control": {
|
|
1779
|
+
schema: { type: "string" },
|
|
1780
|
+
example: "no-store"
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
},
|
|
1784
|
+
"429": {
|
|
1785
|
+
description: "Rate limit exceeded; do not retry through another issuer.",
|
|
1786
|
+
headers: {
|
|
1787
|
+
"Cache-Control": {
|
|
1788
|
+
schema: { type: "string" },
|
|
1789
|
+
example: "no-store"
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
},
|
|
1793
|
+
"503": {
|
|
1794
|
+
description: "Authentication backend, serving metadata or issued keyring unavailable.",
|
|
1795
|
+
headers: {
|
|
1796
|
+
"Cache-Control": {
|
|
1797
|
+
schema: { type: "string" },
|
|
1798
|
+
example: "no-store"
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
},
|
|
1803
|
+
"x-rstream-required-rules": [
|
|
1804
|
+
{ resource: "engine.turn-server.credentials", action: "create" }
|
|
1805
|
+
]
|
|
1806
|
+
}
|
|
1807
|
+
},
|
|
1488
1808
|
"/api/openapi.json": {
|
|
1489
1809
|
get: {
|
|
1490
1810
|
tags: ["Discovery"],
|
|
@@ -1511,7 +1831,7 @@ var engineOpenApiDocument = {
|
|
|
1511
1831
|
summary: "List visible engine clients.",
|
|
1512
1832
|
operationId: "list_engine_clients",
|
|
1513
1833
|
parameters: [{ $ref: "#/components/parameters/ListClientsParams" }],
|
|
1514
|
-
security: [{ bearerAuth: [] }],
|
|
1834
|
+
security: [{ bearerAuth: [] }, { mutualTLS: [] }],
|
|
1515
1835
|
responses: {
|
|
1516
1836
|
"200": jsonArrayResponse(
|
|
1517
1837
|
"Visible clients.",
|
|
@@ -1530,7 +1850,7 @@ var engineOpenApiDocument = {
|
|
|
1530
1850
|
summary: "List visible engine tunnels.",
|
|
1531
1851
|
operationId: "list_engine_tunnels",
|
|
1532
1852
|
parameters: [{ $ref: "#/components/parameters/ListTunnelsParams" }],
|
|
1533
|
-
security: [{ bearerAuth: [] }],
|
|
1853
|
+
security: [{ bearerAuth: [] }, { mutualTLS: [] }],
|
|
1534
1854
|
responses: {
|
|
1535
1855
|
"200": jsonArrayResponse(
|
|
1536
1856
|
"Visible tunnels. Fine-grained list scopes may filter objects and project fields.",
|
|
@@ -1550,7 +1870,7 @@ var engineOpenApiDocument = {
|
|
|
1550
1870
|
summary: "Subscribe to visible engine state through Server-Sent Events.",
|
|
1551
1871
|
operationId: "watch_engine_events_sse",
|
|
1552
1872
|
parameters: [{ $ref: "#/components/parameters/WatchParams" }],
|
|
1553
|
-
security: [{ bearerAuth: [] }],
|
|
1873
|
+
security: [{ bearerAuth: [] }, { mutualTLS: [] }],
|
|
1554
1874
|
responses: {
|
|
1555
1875
|
"200": streamResponse(
|
|
1556
1876
|
"SSE stream. Each event is emitted as a data line containing a JSON Event object."
|
|
@@ -1569,7 +1889,7 @@ var engineOpenApiDocument = {
|
|
|
1569
1889
|
summary: "Subscribe to visible engine state through a WebSocket upgrade.",
|
|
1570
1890
|
operationId: "watch_engine_events_websocket",
|
|
1571
1891
|
parameters: [{ $ref: "#/components/parameters/WatchParams" }],
|
|
1572
|
-
security: [{ bearerAuth: [] }],
|
|
1892
|
+
security: [{ bearerAuth: [] }, { mutualTLS: [] }],
|
|
1573
1893
|
responses: {
|
|
1574
1894
|
"101": {
|
|
1575
1895
|
description: "WebSocket connection accepted. Frames contain JSON Event objects."
|
|
@@ -1593,6 +1913,113 @@ var engineOpenApiJson = `${JSON.stringify(
|
|
|
1593
1913
|
|
|
1594
1914
|
// src/resolution.ts
|
|
1595
1915
|
var import_rstream3 = require("@rstreamlabs/rstream");
|
|
1916
|
+
|
|
1917
|
+
// src/http-lifetime.ts
|
|
1918
|
+
var requestLifetime = (parents, milliseconds) => {
|
|
1919
|
+
const controller = new AbortController();
|
|
1920
|
+
const signals = parents.filter((signal) => signal != null);
|
|
1921
|
+
const listeners = signals.map((signal) => ({
|
|
1922
|
+
signal,
|
|
1923
|
+
abort: () => controller.abort(signal.reason)
|
|
1924
|
+
}));
|
|
1925
|
+
for (const listener of listeners) {
|
|
1926
|
+
if (listener.signal.aborted) listener.abort();
|
|
1927
|
+
else
|
|
1928
|
+
listener.signal.addEventListener("abort", listener.abort, { once: true });
|
|
1929
|
+
}
|
|
1930
|
+
const timer = setTimeout(
|
|
1931
|
+
() => controller.abort(
|
|
1932
|
+
new DOMException("Engine request timed out.", "TimeoutError")
|
|
1933
|
+
),
|
|
1934
|
+
milliseconds
|
|
1935
|
+
);
|
|
1936
|
+
return {
|
|
1937
|
+
signal: controller.signal,
|
|
1938
|
+
stopTimer: () => clearTimeout(timer),
|
|
1939
|
+
abort: () => controller.abort(
|
|
1940
|
+
new DOMException("Engine request closed.", "AbortError")
|
|
1941
|
+
),
|
|
1942
|
+
close: () => {
|
|
1943
|
+
clearTimeout(timer);
|
|
1944
|
+
for (const listener of listeners)
|
|
1945
|
+
listener.signal.removeEventListener("abort", listener.abort);
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
1948
|
+
};
|
|
1949
|
+
var readChunks = async (reader, limit, chunks, size) => {
|
|
1950
|
+
const result = await reader.read();
|
|
1951
|
+
if (result.done) return chunks;
|
|
1952
|
+
const total = size + result.value.byteLength;
|
|
1953
|
+
if (total > limit) {
|
|
1954
|
+
await reader.cancel();
|
|
1955
|
+
throw new Error("HTTP response exceeds the size limit.");
|
|
1956
|
+
}
|
|
1957
|
+
chunks.push(result.value);
|
|
1958
|
+
return readChunks(reader, limit, chunks, total);
|
|
1959
|
+
};
|
|
1960
|
+
var readBoundedText = async (response, limit) => {
|
|
1961
|
+
if (Number(response.headers.get("content-length")) > limit) {
|
|
1962
|
+
await response.body?.cancel();
|
|
1963
|
+
throw new Error("HTTP response exceeds the size limit.");
|
|
1964
|
+
}
|
|
1965
|
+
if (!response.body) return "";
|
|
1966
|
+
const reader = response.body.getReader();
|
|
1967
|
+
const chunks = await readChunks(reader, limit, [], 0).finally(
|
|
1968
|
+
() => reader.releaseLock()
|
|
1969
|
+
);
|
|
1970
|
+
const bytes = new Uint8Array(
|
|
1971
|
+
chunks.reduce((size, chunk) => size + chunk.byteLength, 0)
|
|
1972
|
+
);
|
|
1973
|
+
chunks.reduce((offset, chunk) => {
|
|
1974
|
+
bytes.set(chunk, offset);
|
|
1975
|
+
return offset + chunk.byteLength;
|
|
1976
|
+
}, 0);
|
|
1977
|
+
return new TextDecoder().decode(bytes);
|
|
1978
|
+
};
|
|
1979
|
+
var waitWithSignal = async (promise, signal) => {
|
|
1980
|
+
if (!signal) return promise;
|
|
1981
|
+
if (signal.aborted) {
|
|
1982
|
+
void promise.catch(() => void 0);
|
|
1983
|
+
signal.throwIfAborted();
|
|
1984
|
+
}
|
|
1985
|
+
return new Promise((resolve, reject) => {
|
|
1986
|
+
const abort = () => reject(signal.reason);
|
|
1987
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1988
|
+
void promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
1989
|
+
});
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1992
|
+
// src/control-plane-fetch.ts
|
|
1993
|
+
function createBoundedControlPlaneFetch(timeoutMilliseconds, parent, fetch2 = globalThis.fetch, maxResponseBytes = 16384) {
|
|
1994
|
+
return async (input, init) => {
|
|
1995
|
+
const request2 = new Request(input, init);
|
|
1996
|
+
const headers = new Headers(request2.headers);
|
|
1997
|
+
headers.set("Connection", "close");
|
|
1998
|
+
const lifetime = requestLifetime(
|
|
1999
|
+
[parent, request2.signal],
|
|
2000
|
+
timeoutMilliseconds
|
|
2001
|
+
);
|
|
2002
|
+
try {
|
|
2003
|
+
lifetime.signal.throwIfAborted();
|
|
2004
|
+
const response = await fetch2(input, {
|
|
2005
|
+
...init,
|
|
2006
|
+
redirect: "manual",
|
|
2007
|
+
headers,
|
|
2008
|
+
signal: lifetime.signal
|
|
2009
|
+
});
|
|
2010
|
+
const responseBody2 = response.body === null ? null : await readBoundedText(response, maxResponseBytes);
|
|
2011
|
+
return new Response(responseBody2, {
|
|
2012
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
2013
|
+
status: response.status,
|
|
2014
|
+
statusText: response.statusText
|
|
2015
|
+
});
|
|
2016
|
+
} finally {
|
|
2017
|
+
lifetime.close();
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// src/resolution.ts
|
|
1596
2023
|
var import_rstream4 = require("@rstreamlabs/rstream");
|
|
1597
2024
|
var import_rstream5 = require("@rstreamlabs/rstream");
|
|
1598
2025
|
var import_rstream6 = require("@rstreamlabs/rstream");
|
|
@@ -1726,6 +2153,12 @@ async function resolveManagedTunnelsProject(config) {
|
|
|
1726
2153
|
);
|
|
1727
2154
|
}
|
|
1728
2155
|
const controlPlane = new import_rstream7.RstreamClient({
|
|
2156
|
+
fetch: createBoundedControlPlaneFetch(
|
|
2157
|
+
1e4,
|
|
2158
|
+
void 0,
|
|
2159
|
+
globalThis.fetch,
|
|
2160
|
+
1024 * 1024
|
|
2161
|
+
),
|
|
1729
2162
|
apiUrl: resolveTunnelsAPIURL(config.apiUrl),
|
|
1730
2163
|
controlPlaneHeaders: config.controlPlaneHeaders,
|
|
1731
2164
|
credentials
|
|
@@ -1766,62 +2199,231 @@ var RstreamTunnelsResource = class {
|
|
|
1766
2199
|
};
|
|
1767
2200
|
|
|
1768
2201
|
// src/tunnels.ts
|
|
1769
|
-
var import_rstream12 = require("@rstreamlabs/rstream");
|
|
1770
|
-
var import_rstream13 = require("@rstreamlabs/rstream");
|
|
1771
2202
|
var import_rstream14 = require("@rstreamlabs/rstream");
|
|
2203
|
+
var import_rstream15 = require("@rstreamlabs/rstream");
|
|
1772
2204
|
|
|
1773
|
-
// src/
|
|
2205
|
+
// src/engine-http.ts
|
|
1774
2206
|
var import_rstream8 = require("@rstreamlabs/rstream");
|
|
1775
|
-
var
|
|
1776
|
-
var
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
);
|
|
1792
|
-
return
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
2207
|
+
var import_zod = require("zod");
|
|
2208
|
+
var discoverySchema = import_zod.z.object({
|
|
2209
|
+
version: import_zod.z.literal(1),
|
|
2210
|
+
projectEndpoint: import_zod.z.string(),
|
|
2211
|
+
mtlsApiUrl: import_zod.z.string(),
|
|
2212
|
+
capabilities: import_zod.z.array(import_zod.z.string()).max(32)
|
|
2213
|
+
});
|
|
2214
|
+
var hasClientCertificate = (tls) => tls?.cert !== void 0 || tls?.key !== void 0;
|
|
2215
|
+
var validateMTLSURL = (target, source) => {
|
|
2216
|
+
const url = new URL(target);
|
|
2217
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash || url.pathname !== "/api" || url.hostname.split(".")[0] !== source.hostname.split(".")[0])
|
|
2218
|
+
throw new Error("Invalid discovered mTLS Engine API URL.");
|
|
2219
|
+
normalizeEngineAddress(url.host);
|
|
2220
|
+
return url.href;
|
|
2221
|
+
};
|
|
2222
|
+
var discoveryCacheMilliseconds = (response) => {
|
|
2223
|
+
const cache = response.headers.get("cache-control") ?? "";
|
|
2224
|
+
if (/\bno-(?:store|cache)\b/i.test(cache)) return 0;
|
|
2225
|
+
const seconds = cache.match(/\bmax-age\s*=\s*"?(\d+)/i)?.[1];
|
|
2226
|
+
return Math.min(seconds === void 0 ? 3600 : Number(seconds), 86400) * 1e3;
|
|
2227
|
+
};
|
|
2228
|
+
var EngineHTTP = class {
|
|
2229
|
+
constructor(options) {
|
|
2230
|
+
this.options = options;
|
|
2231
|
+
if (hasClientCertificate(options.tls) && (!options.tls?.cert || !options.tls?.key))
|
|
2232
|
+
throw new Error("mTLS requires both a certificate and its private key.");
|
|
2233
|
+
}
|
|
2234
|
+
options;
|
|
2235
|
+
closed = new AbortController();
|
|
2236
|
+
authenticated;
|
|
2237
|
+
anonymous;
|
|
2238
|
+
discovery;
|
|
2239
|
+
flight;
|
|
2240
|
+
get usesMTLS() {
|
|
2241
|
+
return hasClientCertificate(this.options.tls);
|
|
2242
|
+
}
|
|
2243
|
+
async close() {
|
|
2244
|
+
this.closed.abort(new Error("Engine HTTP client is closed."));
|
|
2245
|
+
this.discovery = void 0;
|
|
2246
|
+
await Promise.all([
|
|
2247
|
+
this.authenticated?.then((client) => client.close()),
|
|
2248
|
+
this.anonymous?.then((client) => client.close())
|
|
2249
|
+
]);
|
|
2250
|
+
}
|
|
2251
|
+
async transport(anonymous) {
|
|
2252
|
+
this.closed.signal.throwIfAborted();
|
|
2253
|
+
if (!anonymous && this.options.fetch) return this.options.fetch;
|
|
2254
|
+
if (!this.options.tls) return globalThis.fetch;
|
|
2255
|
+
if (anonymous) {
|
|
2256
|
+
this.anonymous ??= Promise.resolve().then(() => (init_node_engine_http(), node_engine_http_exports)).then(
|
|
2257
|
+
({ NodeEngineHTTP: NodeEngineHTTP2 }) => new NodeEngineHTTP2({
|
|
2258
|
+
ca: this.options.tls?.ca,
|
|
2259
|
+
rejectUnauthorized: true
|
|
2260
|
+
})
|
|
2261
|
+
);
|
|
2262
|
+
return (await this.anonymous).fetch;
|
|
2263
|
+
}
|
|
2264
|
+
this.authenticated ??= Promise.resolve().then(() => (init_node_engine_http(), node_engine_http_exports)).then(
|
|
2265
|
+
({ NodeEngineHTTP: NodeEngineHTTP2 }) => new NodeEngineHTTP2(this.options.tls ?? {})
|
|
2266
|
+
);
|
|
2267
|
+
return (await this.authenticated).fetch;
|
|
2268
|
+
}
|
|
2269
|
+
async apiURL(engine, signal) {
|
|
2270
|
+
this.closed.signal.throwIfAborted();
|
|
2271
|
+
signal?.throwIfAborted();
|
|
2272
|
+
const source = new URL(`https://${normalizeEngineAddress(engine)}`);
|
|
2273
|
+
if (!this.usesMTLS) return `https://${normalizeEngineAddress(engine)}/api`;
|
|
2274
|
+
if (this.options.mtlsApiUrl)
|
|
2275
|
+
return validateMTLSURL(this.options.mtlsApiUrl, source);
|
|
2276
|
+
if (this.discovery?.source === source.origin && this.discovery.expiresAt > Date.now())
|
|
2277
|
+
return this.discovery.url;
|
|
2278
|
+
const flight = this.flight?.source === source.origin ? this.flight : { source: source.origin, promise: this.discover(source) };
|
|
2279
|
+
this.flight = flight;
|
|
2280
|
+
try {
|
|
2281
|
+
const discovery = await waitWithSignal(flight.promise, signal);
|
|
2282
|
+
this.discovery = discovery;
|
|
2283
|
+
return discovery.url;
|
|
2284
|
+
} finally {
|
|
2285
|
+
void flight.promise.then(
|
|
2286
|
+
() => {
|
|
2287
|
+
if (this.flight === flight) this.flight = void 0;
|
|
2288
|
+
},
|
|
2289
|
+
() => {
|
|
2290
|
+
if (this.flight === flight) this.flight = void 0;
|
|
2291
|
+
}
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
async discover(source) {
|
|
2296
|
+
const lifetime = requestLifetime([this.closed.signal], 5e3);
|
|
2297
|
+
try {
|
|
2298
|
+
const fetch2 = await this.transport(true);
|
|
2299
|
+
const response = await fetch2(
|
|
2300
|
+
new URL("/.well-known/rstream/engine", source),
|
|
2301
|
+
{
|
|
2302
|
+
method: "GET",
|
|
2303
|
+
credentials: "omit",
|
|
2304
|
+
redirect: "manual",
|
|
2305
|
+
signal: lifetime.signal
|
|
2306
|
+
}
|
|
2307
|
+
);
|
|
2308
|
+
if (!response.ok) {
|
|
2309
|
+
await response.body?.cancel();
|
|
2310
|
+
throw new import_rstream8.RstreamHTTPError(
|
|
2311
|
+
response.status,
|
|
2312
|
+
"mTLS Engine discovery failed."
|
|
2313
|
+
);
|
|
2314
|
+
}
|
|
2315
|
+
const text = await readBoundedText(response, 16384);
|
|
2316
|
+
const value = JSON.parse(text);
|
|
2317
|
+
const document = discoverySchema.parse(value);
|
|
2318
|
+
if (document.projectEndpoint !== source.hostname.split(".")[0] || !document.capabilities.includes("engine-api-mtls"))
|
|
2319
|
+
throw new Error(
|
|
2320
|
+
"Engine discovery does not advertise mTLS for this project."
|
|
2321
|
+
);
|
|
2322
|
+
return {
|
|
2323
|
+
source: source.origin,
|
|
2324
|
+
url: validateMTLSURL(document.mtlsApiUrl, source),
|
|
2325
|
+
expiresAt: Date.now() + discoveryCacheMilliseconds(response)
|
|
2326
|
+
};
|
|
2327
|
+
} finally {
|
|
2328
|
+
lifetime.close();
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
async eventStream(engine, options) {
|
|
2332
|
+
const lifetime = requestLifetime(
|
|
2333
|
+
[this.closed.signal, options.signal],
|
|
2334
|
+
1e4
|
|
2335
|
+
);
|
|
1806
2336
|
try {
|
|
1807
|
-
const
|
|
2337
|
+
const base = await this.apiURL(engine, lifetime.signal);
|
|
2338
|
+
const headers = new Headers(options.headers);
|
|
2339
|
+
if (this.usesMTLS && headers.has("authorization"))
|
|
2340
|
+
throw new Error("Use a token or mTLS, not both.");
|
|
2341
|
+
headers.set("Accept", "text/event-stream");
|
|
2342
|
+
const fetch2 = await this.transport(false);
|
|
2343
|
+
const response = await fetch2(`${base}/sse`, {
|
|
2344
|
+
method: "GET",
|
|
1808
2345
|
headers,
|
|
2346
|
+
credentials: "omit",
|
|
2347
|
+
redirect: "manual",
|
|
1809
2348
|
signal: lifetime.signal
|
|
1810
2349
|
});
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
2350
|
+
if (!response.ok)
|
|
2351
|
+
throw new import_rstream8.RstreamHTTPError(
|
|
2352
|
+
response.status,
|
|
2353
|
+
await readBoundedText(response, 16384)
|
|
2354
|
+
);
|
|
2355
|
+
if (!response.headers.get("content-type")?.startsWith("text/event-stream"))
|
|
2356
|
+
throw new Error("Engine returned an invalid event stream.");
|
|
2357
|
+
lifetime.stopTimer();
|
|
2358
|
+
return {
|
|
2359
|
+
response,
|
|
2360
|
+
close: () => {
|
|
2361
|
+
lifetime.abort();
|
|
2362
|
+
lifetime.close();
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
} catch (error) {
|
|
2366
|
+
lifetime.abort();
|
|
2367
|
+
lifetime.close();
|
|
2368
|
+
if (!(error instanceof import_rstream8.RstreamHTTPError)) this.discovery = void 0;
|
|
2369
|
+
throw error;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
async response(engine, path, options = {}, maxBytes = 16 * 1024 * 1024) {
|
|
2373
|
+
if (!path.startsWith("/") || path.startsWith("//") || path.includes("\\"))
|
|
2374
|
+
throw new Error("Engine request path must be a relative absolute path.");
|
|
2375
|
+
const lifetime = requestLifetime(
|
|
2376
|
+
[this.closed.signal, options.signal],
|
|
2377
|
+
1e4
|
|
2378
|
+
);
|
|
2379
|
+
try {
|
|
2380
|
+
const base = await this.apiURL(engine, lifetime.signal);
|
|
2381
|
+
const url = new URL(`${base}${path}`);
|
|
2382
|
+
if (url.origin !== new URL(base).origin || !url.pathname.startsWith("/api/") || url.username || url.password)
|
|
2383
|
+
throw new Error(
|
|
2384
|
+
"Engine request path must remain inside the Engine API."
|
|
2385
|
+
);
|
|
2386
|
+
const headers = new Headers(options.headers);
|
|
2387
|
+
if (this.usesMTLS && (headers.has("authorization") || url.searchParams.has("rstream.token")))
|
|
2388
|
+
throw new Error("Use a token or mTLS, not both.");
|
|
2389
|
+
const fetch2 = await this.transport(false);
|
|
2390
|
+
const response = await fetch2(`${base}${path}`, {
|
|
2391
|
+
...options,
|
|
2392
|
+
headers,
|
|
2393
|
+
credentials: "omit",
|
|
2394
|
+
redirect: "manual",
|
|
2395
|
+
signal: lifetime.signal
|
|
2396
|
+
});
|
|
2397
|
+
const text = await readBoundedText(
|
|
2398
|
+
response,
|
|
2399
|
+
response.ok ? maxBytes : 16384
|
|
2400
|
+
);
|
|
2401
|
+
if (!response.ok) throw new import_rstream8.RstreamHTTPError(response.status, text);
|
|
2402
|
+
return new Response([204, 205].includes(response.status) ? null : text, {
|
|
1814
2403
|
status: response.status,
|
|
1815
|
-
|
|
2404
|
+
headers: response.headers
|
|
1816
2405
|
});
|
|
2406
|
+
} catch (error) {
|
|
2407
|
+
if (!(error instanceof import_rstream8.RstreamHTTPError)) this.discovery = void 0;
|
|
2408
|
+
throw error;
|
|
1817
2409
|
} finally {
|
|
1818
2410
|
lifetime.close();
|
|
1819
2411
|
}
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2414
|
+
|
|
2415
|
+
// src/tunnels.ts
|
|
2416
|
+
var import_rstream16 = require("@rstreamlabs/rstream");
|
|
2417
|
+
var import_rstream17 = require("@rstreamlabs/rstream");
|
|
1822
2418
|
|
|
1823
2419
|
// src/turn.ts
|
|
1824
|
-
var
|
|
2420
|
+
var import_rstream9 = require("@rstreamlabs/rstream");
|
|
2421
|
+
var import_rstream10 = require("@rstreamlabs/rstream");
|
|
2422
|
+
var import_rstream11 = require("@rstreamlabs/rstream");
|
|
2423
|
+
var import_rstream12 = require("@rstreamlabs/rstream");
|
|
2424
|
+
var import_rstream13 = require("@rstreamlabs/rstream");
|
|
2425
|
+
var import_turn = require("@rstreamlabs/rstream/turn");
|
|
2426
|
+
var z9 = __toESM(require("zod"));
|
|
1825
2427
|
var import_crypto = __toESM(require("crypto"));
|
|
1826
2428
|
var defaultTURNPort = 3478;
|
|
1827
2429
|
var defaultTURNSPort = 5349;
|
|
@@ -1830,20 +2432,31 @@ var maxTURNCredentialTTLSeconds = 60 * 60;
|
|
|
1830
2432
|
var maxTURNKeyringBytes = 8 * 1024;
|
|
1831
2433
|
var defaultAPIRequestTimeoutMilliseconds = 1e4;
|
|
1832
2434
|
var maxAPIRequestTimeoutMilliseconds = 6e4;
|
|
1833
|
-
var turnTokenClaimsSchema =
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
2435
|
+
var turnTokenClaimsSchema = z9.object({
|
|
2436
|
+
permissions: z9.array(z9.string()).nullable().optional(),
|
|
2437
|
+
exp: z9.number().int().optional(),
|
|
2438
|
+
iat: z9.number().int().optional(),
|
|
2439
|
+
token_endpoint: z9.string().regex(/^[A-Za-z0-9_.-]{1,128}$/).optional(),
|
|
2440
|
+
tokendpoint: z9.string().regex(/^[A-Za-z0-9_.-]{1,128}$/).optional(),
|
|
2441
|
+
type: z9.enum(["app", "auth", "pat"])
|
|
1839
2442
|
});
|
|
1840
|
-
var turnCredentialModeSchema =
|
|
2443
|
+
var turnCredentialModeSchema = z9.enum([
|
|
2444
|
+
"auto",
|
|
2445
|
+
"api",
|
|
2446
|
+
"engine",
|
|
2447
|
+
"app",
|
|
2448
|
+
"pat"
|
|
2449
|
+
]);
|
|
1841
2450
|
function normalizeOptionalString2(value) {
|
|
1842
2451
|
const normalized = value?.trim();
|
|
1843
2452
|
return normalized ? normalized : void 0;
|
|
1844
2453
|
}
|
|
1845
2454
|
function normalizeClusterDomain(clusterDomain) {
|
|
1846
2455
|
const normalized = normalizeOptionalString2(clusterDomain);
|
|
2456
|
+
if (normalized && (normalized.length > 253 || !normalized.split(".").every(
|
|
2457
|
+
(label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(label)
|
|
2458
|
+
)))
|
|
2459
|
+
throw new Error("TURN domain and realm must be valid DNS names.");
|
|
1847
2460
|
return normalized?.toLowerCase();
|
|
1848
2461
|
}
|
|
1849
2462
|
function normalizeNow(now) {
|
|
@@ -1958,14 +2571,27 @@ function requireOption(value, message) {
|
|
|
1958
2571
|
}
|
|
1959
2572
|
throw new Error(message);
|
|
1960
2573
|
}
|
|
1961
|
-
function
|
|
1962
|
-
|
|
1963
|
-
return
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
return "app";
|
|
2574
|
+
function automaticPATClaims(token) {
|
|
2575
|
+
try {
|
|
2576
|
+
return parseTURNTokenClaims(token);
|
|
2577
|
+
} catch {
|
|
2578
|
+
return void 0;
|
|
1967
2579
|
}
|
|
1968
|
-
|
|
2580
|
+
}
|
|
2581
|
+
function resolveMode(options) {
|
|
2582
|
+
const requested = turnCredentialModeSchema.parse(options.mode ?? "auto");
|
|
2583
|
+
if (requested !== "auto") return requested;
|
|
2584
|
+
if (options.client?.usesMTLS || hasClientCertificate(options.tls))
|
|
2585
|
+
return "engine";
|
|
2586
|
+
const claims = (0, import_rstream11.isTokenCredentials)(options.credentials) ? automaticPATClaims(options.credentials.token) : void 0;
|
|
2587
|
+
if (claims?.token_endpoint && claims.tokendpoint && claims.token_endpoint !== claims.tokendpoint)
|
|
2588
|
+
throw new Error("Conflicting PAT token endpoints.");
|
|
2589
|
+
const localTarget = options.projectEndpoint && (options.clusterDomain || options.turnDomain && options.turnRealm);
|
|
2590
|
+
if (!localTarget) return "auto";
|
|
2591
|
+
if ((0, import_rstream9.isClientCredentials)(options.credentials)) return "app";
|
|
2592
|
+
if (claims?.type === "pat" && claims.exp !== void 0 && (claims.token_endpoint || claims.tokendpoint) && (claims.permissions === null || claims.permissions?.includes("turn.relay.allocate")))
|
|
2593
|
+
return "pat";
|
|
2594
|
+
return "auto";
|
|
1969
2595
|
}
|
|
1970
2596
|
async function readBoundedTURNKeyring(reader, chunks = [], size = 0) {
|
|
1971
2597
|
const result = await reader.read();
|
|
@@ -1985,7 +2611,7 @@ async function readBoundedTURNKeyring(reader, chunks = [], size = 0) {
|
|
|
1985
2611
|
}
|
|
1986
2612
|
function parseTURNServerPublicKeyHex(value) {
|
|
1987
2613
|
const normalized = value.trim();
|
|
1988
|
-
if (normalized.length === 0 || normalized.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(normalized)) {
|
|
2614
|
+
if (normalized.length === 0 || normalized.length > maxTURNKeyringBytes || normalized.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(normalized)) {
|
|
1989
2615
|
throw new Error("TURN keyring must contain hexadecimal DER key material.");
|
|
1990
2616
|
}
|
|
1991
2617
|
try {
|
|
@@ -2008,6 +2634,10 @@ function parseTURNServerPublicKeyHex(value) {
|
|
|
2008
2634
|
}
|
|
2009
2635
|
}
|
|
2010
2636
|
function parseAPPPrivateKey(value) {
|
|
2637
|
+
if (value.length > 16384 || value.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(value))
|
|
2638
|
+
throw new Error(
|
|
2639
|
+
"TURN APP client secret must contain hexadecimal DER key material."
|
|
2640
|
+
);
|
|
2011
2641
|
try {
|
|
2012
2642
|
const key = import_crypto.default.createPrivateKey({
|
|
2013
2643
|
key: Buffer.from(value, "hex"),
|
|
@@ -2043,19 +2673,38 @@ async function loadTURNServerPublicKeyHex(clusterDomain, options) {
|
|
|
2043
2673
|
`/keyrings/turn/${clusterDomain}.spki.der.hex`,
|
|
2044
2674
|
keyringBaseURL
|
|
2045
2675
|
);
|
|
2046
|
-
|
|
2047
|
-
if (response.status >= 300 && response.status < 400) {
|
|
2676
|
+
if (url.protocol !== "https:" || url.username || url.password)
|
|
2048
2677
|
throw new Error(
|
|
2049
|
-
"TURN keyring
|
|
2678
|
+
"TURN keyring requests require HTTPS without URL credentials."
|
|
2050
2679
|
);
|
|
2680
|
+
const lifetime = requestLifetime(
|
|
2681
|
+
[options.signal],
|
|
2682
|
+
normalizeAPIRequestTimeoutMilliseconds(options.requestTimeoutMilliseconds)
|
|
2683
|
+
);
|
|
2684
|
+
try {
|
|
2685
|
+
const response = await fetch(url, {
|
|
2686
|
+
redirect: "manual",
|
|
2687
|
+
credentials: "omit",
|
|
2688
|
+
signal: lifetime.signal
|
|
2689
|
+
});
|
|
2690
|
+
if (response.status >= 300 && response.status < 400) {
|
|
2691
|
+
await response.body?.cancel();
|
|
2692
|
+
throw new Error(
|
|
2693
|
+
"TURN keyring request was redirected; redirects are refused."
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
if (!response.ok) {
|
|
2697
|
+
await response.body?.cancel();
|
|
2698
|
+
throw new Error(`TURN keyring request returned HTTP ${response.status}.`);
|
|
2699
|
+
}
|
|
2700
|
+
if (!response.body) {
|
|
2701
|
+
throw new Error("TURN keyring response body is missing.");
|
|
2702
|
+
}
|
|
2703
|
+
const reader = response.body.getReader();
|
|
2704
|
+
return (await readBoundedTURNKeyring(reader).finally(() => reader.releaseLock())).trim();
|
|
2705
|
+
} finally {
|
|
2706
|
+
lifetime.close();
|
|
2051
2707
|
}
|
|
2052
|
-
if (!response.ok) {
|
|
2053
|
-
throw new Error(`TURN keyring request returned HTTP ${response.status}.`);
|
|
2054
|
-
}
|
|
2055
|
-
if (!response.body) {
|
|
2056
|
-
throw new Error("TURN keyring response body is missing.");
|
|
2057
|
-
}
|
|
2058
|
-
return (await readBoundedTURNKeyring(response.body.getReader())).trim();
|
|
2059
2708
|
}
|
|
2060
2709
|
async function createAPITURNCredentials(options) {
|
|
2061
2710
|
const projectId = normalizeOptionalString2(options.projectId);
|
|
@@ -2069,11 +2718,21 @@ async function createAPITURNCredentials(options) {
|
|
|
2069
2718
|
options.requestTimeoutMilliseconds
|
|
2070
2719
|
);
|
|
2071
2720
|
const params = createAPITURNCredentialsParams(options.ttlSeconds);
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2721
|
+
const request2 = createBoundedControlPlaneFetch(
|
|
2722
|
+
requestTimeoutMilliseconds,
|
|
2723
|
+
options.signal,
|
|
2724
|
+
options.fetch
|
|
2725
|
+
);
|
|
2726
|
+
const client = new import_rstream12.RstreamClient({
|
|
2727
|
+
controlPlaneHeaders: options.controlPlaneHeaders,
|
|
2074
2728
|
apiUrl: options.apiUrl,
|
|
2075
|
-
credentials: options.credentials
|
|
2076
|
-
|
|
2729
|
+
credentials: (0, import_rstream9.isClientCredentials)(options.credentials) ? {
|
|
2730
|
+
token: (0, import_rstream10.createClientCredentialsToken)(options.credentials, {
|
|
2731
|
+
expiresInSeconds: 60,
|
|
2732
|
+
claims: { permissions: ["turn.credentials.create"] }
|
|
2733
|
+
}).token
|
|
2734
|
+
} : options.credentials,
|
|
2735
|
+
fetch: request2
|
|
2077
2736
|
});
|
|
2078
2737
|
if (projectId) {
|
|
2079
2738
|
return await client.tunnels.projects.createTurnCredentials(
|
|
@@ -2116,6 +2775,8 @@ function createPATTURNCredentials(options) {
|
|
|
2116
2775
|
const claimsTokenEndpoint = normalizeOptionalString2(
|
|
2117
2776
|
claims.token_endpoint ?? claims.tokendpoint
|
|
2118
2777
|
);
|
|
2778
|
+
if (claims.token_endpoint && claims.tokendpoint && claims.token_endpoint !== claims.tokendpoint)
|
|
2779
|
+
throw new Error("Conflicting PAT token endpoints.");
|
|
2119
2780
|
const configuredTokenEndpoint = normalizeOptionalString2(
|
|
2120
2781
|
options.tokenEndpoint
|
|
2121
2782
|
);
|
|
@@ -2125,6 +2786,8 @@ function createPATTURNCredentials(options) {
|
|
|
2125
2786
|
"TURN PAT mode requires a PAT token carrying a token endpoint."
|
|
2126
2787
|
);
|
|
2127
2788
|
}
|
|
2789
|
+
requireTURNReference(projectEndpoint);
|
|
2790
|
+
requireTURNReference(tokenEndpoint);
|
|
2128
2791
|
if (configuredTokenEndpoint !== void 0 && claimsTokenEndpoint !== void 0 && claimsTokenEndpoint !== configuredTokenEndpoint) {
|
|
2129
2792
|
throw new Error("Token endpoint does not match the PAT token.");
|
|
2130
2793
|
}
|
|
@@ -2169,6 +2832,8 @@ async function createAPPTURNCredentials(options) {
|
|
|
2169
2832
|
if (!clientId) {
|
|
2170
2833
|
throw new Error("Client ID is required for TURN APP mode.");
|
|
2171
2834
|
}
|
|
2835
|
+
requireTURNReference(projectEndpoint);
|
|
2836
|
+
requireTURNReference(clientId);
|
|
2172
2837
|
const clientSecret = normalizeOptionalString2(options.clientSecret);
|
|
2173
2838
|
if (!clientSecret) {
|
|
2174
2839
|
throw new Error("Client secret is required for TURN APP mode.");
|
|
@@ -2201,32 +2866,47 @@ async function createAPPTURNCredentials(options) {
|
|
|
2201
2866
|
username
|
|
2202
2867
|
});
|
|
2203
2868
|
}
|
|
2204
|
-
async function
|
|
2869
|
+
async function createTURNCredentialsForLifetime(options) {
|
|
2205
2870
|
const credentials = options.credentials;
|
|
2206
|
-
const mode = resolveMode(
|
|
2871
|
+
const mode = resolveMode(options);
|
|
2207
2872
|
const projectEndpoint = normalizeOptionalString2(options.projectEndpoint);
|
|
2208
|
-
if (mode === "
|
|
2873
|
+
if (mode === "engine") return createEngineTURNCredentials(options);
|
|
2874
|
+
if (options.client?.usesMTLS || hasClientCertificate(options.tls))
|
|
2875
|
+
throw new Error(
|
|
2876
|
+
"Certificate authentication requires automatic or Engine TURN mode."
|
|
2877
|
+
);
|
|
2878
|
+
if (mode === "api" || mode === "auto") {
|
|
2209
2879
|
const controlPlaneCredentials = options.controlPlaneCredentials ?? credentials;
|
|
2210
2880
|
if (!controlPlaneCredentials) {
|
|
2881
|
+
if (mode === "auto" && (options.client || options.engine))
|
|
2882
|
+
return createEngineTURNCredentials(options);
|
|
2211
2883
|
throw new Error(
|
|
2212
2884
|
"Control-plane credentials are required for TURN API mode."
|
|
2213
2885
|
);
|
|
2214
2886
|
}
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2887
|
+
try {
|
|
2888
|
+
return await createAPITURNCredentials({
|
|
2889
|
+
controlPlaneHeaders: options.controlPlaneHeaders,
|
|
2890
|
+
apiUrl: options.apiUrl,
|
|
2891
|
+
credentials: controlPlaneCredentials,
|
|
2892
|
+
fetch: options.fetch,
|
|
2893
|
+
projectEndpoint,
|
|
2894
|
+
projectId: options.projectId,
|
|
2895
|
+
requestTimeoutMilliseconds: mode === "auto" ? Math.min(options.requestTimeoutMilliseconds ?? 1e4, 5e3) : options.requestTimeoutMilliseconds,
|
|
2896
|
+
signal: options.signal,
|
|
2897
|
+
ttlSeconds: options.ttlSeconds
|
|
2898
|
+
});
|
|
2899
|
+
} catch (error) {
|
|
2900
|
+
if (mode !== "auto" || !projectEndpoint || !(options.client?.configuredEngine || options.engine) || !turnEngineFallbackAllowed(error, options.signal))
|
|
2901
|
+
throw error;
|
|
2902
|
+
return createEngineTURNCredentials(options);
|
|
2903
|
+
}
|
|
2224
2904
|
}
|
|
2225
2905
|
const legacyDomain = normalizeClusterDomain(options.clusterDomain);
|
|
2226
2906
|
const turnDomain = normalizeClusterDomain(options.turnDomain) ?? legacyDomain ?? (projectEndpoint && options.engine ? deriveClusterDomainFromEngine(projectEndpoint, options.engine) : void 0);
|
|
2227
2907
|
const turnRealm = normalizeClusterDomain(options.turnRealm) ?? legacyDomain;
|
|
2228
2908
|
if (mode === "pat") {
|
|
2229
|
-
if (!(0,
|
|
2909
|
+
if (!(0, import_rstream11.isTokenCredentials)(credentials)) {
|
|
2230
2910
|
throw new Error("A PAT token is required for TURN PAT mode.");
|
|
2231
2911
|
}
|
|
2232
2912
|
return createPATTURNCredentials({
|
|
@@ -2250,10 +2930,12 @@ async function createTURNCredentials(options) {
|
|
|
2250
2930
|
turnsPort: options.turnsPort
|
|
2251
2931
|
});
|
|
2252
2932
|
}
|
|
2253
|
-
if (!(0,
|
|
2933
|
+
if (!(0, import_rstream9.isClientCredentials)(credentials)) {
|
|
2254
2934
|
throw new Error("Client credentials are required for TURN APP mode.");
|
|
2255
2935
|
}
|
|
2256
2936
|
return await createAPPTURNCredentials({
|
|
2937
|
+
signal: options.signal,
|
|
2938
|
+
requestTimeoutMilliseconds: options.requestTimeoutMilliseconds,
|
|
2257
2939
|
apiUrl: options.apiUrl,
|
|
2258
2940
|
clientId: credentials.clientId,
|
|
2259
2941
|
clientSecret: credentials.clientSecret,
|
|
@@ -2277,25 +2959,139 @@ async function createTURNCredentials(options) {
|
|
|
2277
2959
|
turnsPort: options.turnsPort
|
|
2278
2960
|
});
|
|
2279
2961
|
}
|
|
2962
|
+
function requireTURNReference(value) {
|
|
2963
|
+
if (!/^[A-Za-z0-9_.-]{1,128}$/.test(value) || value === "-")
|
|
2964
|
+
throw new Error("Invalid TURN project or credential reference.");
|
|
2965
|
+
}
|
|
2966
|
+
function turnEngineFallbackAllowed(error, signal) {
|
|
2967
|
+
if (signal?.aborted) return false;
|
|
2968
|
+
if (error instanceof import_rstream13.RstreamHTTPError)
|
|
2969
|
+
return [502, 503, 504].includes(error.status);
|
|
2970
|
+
if (error instanceof DOMException && error.name === "TimeoutError")
|
|
2971
|
+
return true;
|
|
2972
|
+
if (typeof error !== "object" || error === null) return false;
|
|
2973
|
+
if ("code" in error && typeof error.code === "string")
|
|
2974
|
+
return [
|
|
2975
|
+
"ENOTFOUND",
|
|
2976
|
+
"EAI_AGAIN",
|
|
2977
|
+
"ECONNREFUSED",
|
|
2978
|
+
"ECONNRESET",
|
|
2979
|
+
"ETIMEDOUT",
|
|
2980
|
+
"EHOSTUNREACH",
|
|
2981
|
+
"ENETUNREACH",
|
|
2982
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
2983
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
2984
|
+
"UND_ERR_SOCKET"
|
|
2985
|
+
].includes(error.code);
|
|
2986
|
+
return "cause" in error && error.cause !== error ? turnEngineFallbackAllowed(error.cause, signal) : false;
|
|
2987
|
+
}
|
|
2988
|
+
async function createEngineTURNCredentials(options) {
|
|
2989
|
+
const client = options.client ?? new RstreamTunnelsClient({
|
|
2990
|
+
credentials: options.credentials,
|
|
2991
|
+
tls: options.tls,
|
|
2992
|
+
mtlsApiUrl: options.mtlsApiUrl,
|
|
2993
|
+
engine: options.engine,
|
|
2994
|
+
projectEndpoint: options.projectEndpoint,
|
|
2995
|
+
projectId: options.projectId
|
|
2996
|
+
});
|
|
2997
|
+
try {
|
|
2998
|
+
const engine = await client.getEngine();
|
|
2999
|
+
const endpoint = new URL(`https://${engine}`).hostname.split(".")[0];
|
|
3000
|
+
if (options.projectEndpoint && options.projectEndpoint !== endpoint || options.projectId && !options.projectEndpoint)
|
|
3001
|
+
throw new Error("TURN project endpoint must match the Engine client.");
|
|
3002
|
+
if (options.engine && options.engine !== engine)
|
|
3003
|
+
throw new Error("TURN Engine must match the reusable client.");
|
|
3004
|
+
const value = await client.request(
|
|
3005
|
+
"/turn-server/credentials",
|
|
3006
|
+
{
|
|
3007
|
+
method: "POST",
|
|
3008
|
+
signal: options.signal,
|
|
3009
|
+
headers: { "Content-Type": "application/json" },
|
|
3010
|
+
body: JSON.stringify(
|
|
3011
|
+
createAPITURNCredentialsParams(options.ttlSeconds)
|
|
3012
|
+
)
|
|
3013
|
+
},
|
|
3014
|
+
16384
|
|
3015
|
+
);
|
|
3016
|
+
return import_turn.turnCredentialsSchema.parse(value);
|
|
3017
|
+
} finally {
|
|
3018
|
+
if (!options.client) await client.close();
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
function validateTURNCredentialsRequest(options) {
|
|
3022
|
+
normalizeTTLSeconds(options.ttlSeconds);
|
|
3023
|
+
turnCredentialModeSchema.parse(options.mode ?? "auto");
|
|
3024
|
+
return normalizeAPIRequestTimeoutMilliseconds(
|
|
3025
|
+
options.requestTimeoutMilliseconds
|
|
3026
|
+
);
|
|
3027
|
+
}
|
|
3028
|
+
async function createTURNCredentials(options) {
|
|
3029
|
+
const lifetime = requestLifetime(
|
|
3030
|
+
[options.signal],
|
|
3031
|
+
validateTURNCredentialsRequest(options)
|
|
3032
|
+
);
|
|
3033
|
+
try {
|
|
3034
|
+
lifetime.signal.throwIfAborted();
|
|
3035
|
+
if ((options.client?.usesMTLS || hasClientCertificate(options.tls)) && options.credentials)
|
|
3036
|
+
throw new Error("Use a token or mTLS, not both.");
|
|
3037
|
+
if (options.client && options.credentials && !sameTURNCredentials(options.client.credentials, options.credentials))
|
|
3038
|
+
throw new Error(
|
|
3039
|
+
"TURN credentials must match the reusable Engine client."
|
|
3040
|
+
);
|
|
3041
|
+
return await waitWithSignal(
|
|
3042
|
+
createTURNCredentialsForLifetime({
|
|
3043
|
+
...options,
|
|
3044
|
+
credentials: options.credentials ?? options.client?.credentials,
|
|
3045
|
+
signal: lifetime.signal
|
|
3046
|
+
}),
|
|
3047
|
+
lifetime.signal
|
|
3048
|
+
);
|
|
3049
|
+
} finally {
|
|
3050
|
+
lifetime.close();
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
function sameTURNCredentials(left, right) {
|
|
3054
|
+
if ((0, import_rstream11.isTokenCredentials)(left) && (0, import_rstream11.isTokenCredentials)(right))
|
|
3055
|
+
return left.token === right.token;
|
|
3056
|
+
return (0, import_rstream9.isClientCredentials)(left) && (0, import_rstream9.isClientCredentials)(right) && left.clientId === right.clientId && left.clientSecret === right.clientSecret;
|
|
3057
|
+
}
|
|
2280
3058
|
|
|
2281
3059
|
// src/turn-resource.ts
|
|
2282
|
-
var import_rstream11 = require("@rstreamlabs/rstream");
|
|
2283
3060
|
var RstreamTURNResource = class {
|
|
2284
3061
|
client;
|
|
2285
3062
|
constructor(client) {
|
|
2286
3063
|
this.client = client;
|
|
2287
3064
|
}
|
|
2288
3065
|
async createCredentials(options = {}) {
|
|
3066
|
+
const lifetime = requestLifetime(
|
|
3067
|
+
[options.signal],
|
|
3068
|
+
validateTURNCredentialsRequest(options)
|
|
3069
|
+
);
|
|
3070
|
+
try {
|
|
3071
|
+
lifetime.signal.throwIfAborted();
|
|
3072
|
+
return await waitWithSignal(
|
|
3073
|
+
this.createForLifetime({ ...options, signal: lifetime.signal }),
|
|
3074
|
+
lifetime.signal
|
|
3075
|
+
);
|
|
3076
|
+
} finally {
|
|
3077
|
+
lifetime.close();
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
async createForLifetime(options) {
|
|
2289
3081
|
const credentials = options.credentials ?? this.client.credentials;
|
|
2290
|
-
const localMode = options.mode === "app" || options.mode === "pat"
|
|
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);
|
|
3082
|
+
const localMode = options.mode === "app" || options.mode === "pat";
|
|
3083
|
+
const target = localMode && !this.client.usesMTLS && (options.turnDomain === void 0 || options.turnRealm === void 0) && options.clusterDomain === void 0 ? await this.client.getTURNTarget() : void 0;
|
|
2293
3084
|
return await createTURNCredentials({
|
|
3085
|
+
client: this.client,
|
|
3086
|
+
signal: options.signal,
|
|
2294
3087
|
apiUrl: options.apiUrl ?? this.client.apiUrl,
|
|
3088
|
+
fetch: options.fetch,
|
|
3089
|
+
requestTimeoutMilliseconds: options.requestTimeoutMilliseconds,
|
|
2295
3090
|
clusterDomain: options.clusterDomain,
|
|
3091
|
+
controlPlaneHeaders: options.controlPlaneHeaders ?? this.client.controlPlaneHeaders,
|
|
2296
3092
|
controlPlaneCredentials: options.controlPlaneCredentials ?? this.client.controlPlaneCredentials,
|
|
2297
3093
|
credentials,
|
|
2298
|
-
engine,
|
|
3094
|
+
engine: options.engine,
|
|
2299
3095
|
keyringBaseUrl: options.keyringBaseUrl,
|
|
2300
3096
|
mode: options.mode,
|
|
2301
3097
|
now: options.now,
|
|
@@ -2464,31 +3260,52 @@ var engineAuthTokenScopes = {
|
|
|
2464
3260
|
var RstreamTunnelsClient = class {
|
|
2465
3261
|
config;
|
|
2466
3262
|
managedProject;
|
|
3263
|
+
http;
|
|
2467
3264
|
constructor(config) {
|
|
2468
3265
|
this.config = config;
|
|
3266
|
+
if (hasClientCertificate(config?.tls) && config?.credentials)
|
|
3267
|
+
throw new Error("Use a token or mTLS, not both.");
|
|
3268
|
+
this.http = new EngineHTTP(config ?? {});
|
|
2469
3269
|
}
|
|
2470
3270
|
get apiUrl() {
|
|
2471
3271
|
return resolveTunnelsAPIURL(this.config?.apiUrl);
|
|
2472
3272
|
}
|
|
2473
3273
|
get credentials() {
|
|
3274
|
+
if (this.http.usesMTLS) return void 0;
|
|
2474
3275
|
return resolveTunnelsCredentials(this.config?.credentials);
|
|
2475
3276
|
}
|
|
2476
3277
|
get controlPlaneCredentials() {
|
|
3278
|
+
if (this.http.usesMTLS) return void 0;
|
|
2477
3279
|
return resolveControlPlaneCredentials(
|
|
2478
3280
|
this.config?.controlPlaneCredentials,
|
|
2479
3281
|
this.credentials
|
|
2480
3282
|
);
|
|
2481
3283
|
}
|
|
3284
|
+
get controlPlaneHeaders() {
|
|
3285
|
+
return this.config?.controlPlaneHeaders;
|
|
3286
|
+
}
|
|
2482
3287
|
get projectEndpoint() {
|
|
2483
3288
|
return this.config?.projectEndpoint;
|
|
2484
3289
|
}
|
|
3290
|
+
get configuredEngine() {
|
|
3291
|
+
return this.config?.engine ?? (0, import_rstream17.readEnvironment)().engine;
|
|
3292
|
+
}
|
|
3293
|
+
get usesMTLS() {
|
|
3294
|
+
return this.http.usesMTLS;
|
|
3295
|
+
}
|
|
3296
|
+
async close() {
|
|
3297
|
+
await this.http.close();
|
|
3298
|
+
}
|
|
3299
|
+
async getEngineAPIURL(signal) {
|
|
3300
|
+
return this.http.apiURL(await this.getEngine(), signal);
|
|
3301
|
+
}
|
|
2485
3302
|
async getEngine() {
|
|
2486
3303
|
const region = resolveTunnelsRegion(this.config?.region);
|
|
2487
3304
|
const projectEndpoint = normalizeOptionalString3(
|
|
2488
3305
|
this.config?.projectEndpoint
|
|
2489
3306
|
);
|
|
2490
3307
|
if (region !== void 0) {
|
|
2491
|
-
if (normalizeOptionalString3(this.config?.engine) !== void 0 || (0,
|
|
3308
|
+
if (normalizeOptionalString3(this.config?.engine) !== void 0 || (0, import_rstream17.readEnvironment)().engine !== void 0) {
|
|
2492
3309
|
throw new Error(
|
|
2493
3310
|
"Region selection cannot be combined with an explicit engine override."
|
|
2494
3311
|
);
|
|
@@ -2500,17 +3317,17 @@ var RstreamTunnelsClient = class {
|
|
|
2500
3317
|
}
|
|
2501
3318
|
const project = await this.getManagedProject();
|
|
2502
3319
|
if (project !== void 0) {
|
|
2503
|
-
const engine = (0,
|
|
3320
|
+
const engine = (0, import_rstream14.getTunnelsProjectEngine)(project, region);
|
|
2504
3321
|
if (engine) return normalizeEngineAddress(engine);
|
|
2505
3322
|
}
|
|
2506
3323
|
throw new Error(
|
|
2507
3324
|
"Failed to resolve the selected regional engine from the managed tunnels project."
|
|
2508
3325
|
);
|
|
2509
3326
|
}
|
|
2510
|
-
if (projectEndpoint !== void 0 && normalizeOptionalString3(this.config?.engine) === void 0 && (0,
|
|
3327
|
+
if (projectEndpoint !== void 0 && normalizeOptionalString3(this.config?.engine) === void 0 && (0, import_rstream17.readEnvironment)().engine === void 0) {
|
|
2511
3328
|
const project = await this.getManagedProject();
|
|
2512
3329
|
if (project !== void 0) {
|
|
2513
|
-
const engine = (0,
|
|
3330
|
+
const engine = (0, import_rstream14.getTunnelsProjectEngine)(project);
|
|
2514
3331
|
if (engine) {
|
|
2515
3332
|
return normalizeEngineAddress(engine);
|
|
2516
3333
|
}
|
|
@@ -2551,6 +3368,10 @@ var RstreamTunnelsClient = class {
|
|
|
2551
3368
|
};
|
|
2552
3369
|
}
|
|
2553
3370
|
async getManagedProject() {
|
|
3371
|
+
if (this.usesMTLS)
|
|
3372
|
+
throw new Error(
|
|
3373
|
+
"mTLS project resolution requires the configured Engine address; the Control plane does not accept certificates."
|
|
3374
|
+
);
|
|
2554
3375
|
const projectEndpoint = normalizeOptionalString3(
|
|
2555
3376
|
this.config?.projectEndpoint
|
|
2556
3377
|
);
|
|
@@ -2626,7 +3447,7 @@ var RstreamTunnelsClient = class {
|
|
|
2626
3447
|
if (!credentials) {
|
|
2627
3448
|
return void 0;
|
|
2628
3449
|
}
|
|
2629
|
-
if (!(0,
|
|
3450
|
+
if (!(0, import_rstream16.isClientCredentials)(credentials)) {
|
|
2630
3451
|
return credentials.token;
|
|
2631
3452
|
}
|
|
2632
3453
|
const tokenParams = await this.getEngineAuthTokenParams();
|
|
@@ -2635,26 +3456,37 @@ var RstreamTunnelsClient = class {
|
|
|
2635
3456
|
engine
|
|
2636
3457
|
})).token;
|
|
2637
3458
|
}
|
|
2638
|
-
async
|
|
3459
|
+
async openEventStream(signal) {
|
|
3460
|
+
const engine = await this.getEngine();
|
|
3461
|
+
const token = await this.getToken(engine);
|
|
3462
|
+
return this.http.eventStream(engine, {
|
|
3463
|
+
signal,
|
|
3464
|
+
headers: token ? { Authorization: `Bearer ${token}` } : void 0
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3467
|
+
async request(path, options, maxResponseBytes) {
|
|
2639
3468
|
const engine = normalizeEngineAddress(await this.getEngine());
|
|
2640
3469
|
if (!path.startsWith("/") || path.startsWith("//")) {
|
|
2641
3470
|
throw new Error("Engine request path must be a relative absolute path.");
|
|
2642
3471
|
}
|
|
2643
|
-
const url = `https://${engine}/api${path}`;
|
|
2644
3472
|
const headers = new Headers(options?.headers || {});
|
|
2645
|
-
const
|
|
3473
|
+
const credentials = this.credentials;
|
|
3474
|
+
const token = path === "/turn-server/credentials" && (0, import_rstream16.isClientCredentials)(credentials) ? (0, import_rstream15.createClientCredentialsToken)(credentials, {
|
|
3475
|
+
expiresInSeconds: 60,
|
|
3476
|
+
claims: { permissions: ["turn.credentials.create"] }
|
|
3477
|
+
}).token : await this.getToken(engine);
|
|
2646
3478
|
if (token) {
|
|
2647
3479
|
headers.set("Authorization", `Bearer ${token}`);
|
|
2648
3480
|
}
|
|
2649
|
-
const response = await
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
3481
|
+
const response = await this.http.response(
|
|
3482
|
+
engine,
|
|
3483
|
+
path,
|
|
3484
|
+
{
|
|
3485
|
+
...options,
|
|
3486
|
+
headers
|
|
3487
|
+
},
|
|
3488
|
+
maxResponseBytes
|
|
3489
|
+
);
|
|
2658
3490
|
return await response.json();
|
|
2659
3491
|
}
|
|
2660
3492
|
};
|
|
@@ -2751,6 +3583,8 @@ function hasWatchOnlyPermissions(permissions) {
|
|
|
2751
3583
|
);
|
|
2752
3584
|
}
|
|
2753
3585
|
var Watch = class {
|
|
3586
|
+
abort = new AbortController();
|
|
3587
|
+
closeStream;
|
|
2754
3588
|
connection = null;
|
|
2755
3589
|
connectionState = "preparing";
|
|
2756
3590
|
config;
|
|
@@ -2765,7 +3599,32 @@ var Watch = class {
|
|
|
2765
3599
|
}
|
|
2766
3600
|
this.connectionState = "connecting";
|
|
2767
3601
|
try {
|
|
3602
|
+
if (this.config.client?.usesMTLS) {
|
|
3603
|
+
if (this.config.auth !== void 0)
|
|
3604
|
+
throw new Error("Watch: Use a token or mTLS, not both.");
|
|
3605
|
+
if (normalizeTransport(this.config.transport) !== "sse")
|
|
3606
|
+
throw new Error(
|
|
3607
|
+
"Watch: Node.js certificate authentication currently requires SSE."
|
|
3608
|
+
);
|
|
3609
|
+
const stream = await this.config.client.openEventStream(
|
|
3610
|
+
this.abort.signal
|
|
3611
|
+
);
|
|
3612
|
+
if (this.abort.signal.aborted) {
|
|
3613
|
+
stream.close();
|
|
3614
|
+
this.abort.signal.throwIfAborted();
|
|
3615
|
+
}
|
|
3616
|
+
this.closeStream = stream.close;
|
|
3617
|
+
if (!stream.response.body)
|
|
3618
|
+
throw new Error("Watch: Engine event stream is empty.");
|
|
3619
|
+
this.connectionState = "connected";
|
|
3620
|
+
this.events.onConnect?.();
|
|
3621
|
+
void this.readCertificateEvents(stream.response.body).finally(
|
|
3622
|
+
() => this.disconnect()
|
|
3623
|
+
);
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
2768
3626
|
const token = typeof this.config.auth === "function" ? await this.config.auth() : this.config.auth;
|
|
3627
|
+
if (!token) throw new Error("Watch: Authentication is required.");
|
|
2769
3628
|
validateWatchQueryToken(token);
|
|
2770
3629
|
const engine = await resolveTunnelsEngine({
|
|
2771
3630
|
apiUrl: this.config.apiUrl,
|
|
@@ -2804,10 +3663,39 @@ var Watch = class {
|
|
|
2804
3663
|
};
|
|
2805
3664
|
}
|
|
2806
3665
|
} catch (error) {
|
|
2807
|
-
this.
|
|
3666
|
+
this.closeStream?.();
|
|
3667
|
+
this.closeStream = void 0;
|
|
3668
|
+
this.connectionState = this.abort.signal.aborted ? "closed" : "preparing";
|
|
2808
3669
|
throw error;
|
|
2809
3670
|
}
|
|
2810
3671
|
}
|
|
3672
|
+
async readCertificateEvents(body) {
|
|
3673
|
+
const reader = body.getReader();
|
|
3674
|
+
const decoder = new TextDecoder();
|
|
3675
|
+
const state = { buffered: "" };
|
|
3676
|
+
try {
|
|
3677
|
+
while (!this.abort.signal.aborted) {
|
|
3678
|
+
const chunk = await reader.read();
|
|
3679
|
+
if (chunk.done) return;
|
|
3680
|
+
state.buffered += decoder.decode(chunk.value, { stream: true });
|
|
3681
|
+
const frames = state.buffered.split(/\r?\n\r?\n/);
|
|
3682
|
+
state.buffered = frames.pop() ?? "";
|
|
3683
|
+
if (state.buffered.length > 1024 * 1024)
|
|
3684
|
+
throw new Error("Watch event exceeds the size limit.");
|
|
3685
|
+
for (const frame of frames) {
|
|
3686
|
+
if (frame.length > 1024 * 1024)
|
|
3687
|
+
throw new Error("Watch event exceeds the size limit.");
|
|
3688
|
+
const data = frame.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
|
|
3689
|
+
if (data) this.handleMessage(data);
|
|
3690
|
+
if (this.abort.signal.aborted) return;
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
} catch {
|
|
3694
|
+
this.disconnect();
|
|
3695
|
+
} finally {
|
|
3696
|
+
reader.releaseLock();
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
2811
3699
|
handleMessage(data) {
|
|
2812
3700
|
try {
|
|
2813
3701
|
const parsed = wsEventsSchema.parse(JSON.parse(data));
|
|
@@ -2817,6 +3705,9 @@ var Watch = class {
|
|
|
2817
3705
|
}
|
|
2818
3706
|
}
|
|
2819
3707
|
disconnect() {
|
|
3708
|
+
this.abort.abort();
|
|
3709
|
+
this.closeStream?.();
|
|
3710
|
+
this.closeStream = void 0;
|
|
2820
3711
|
if (this.connection) {
|
|
2821
3712
|
this.connection.onerror = null;
|
|
2822
3713
|
this.connection.onmessage = null;
|
|
@@ -2832,8 +3723,8 @@ var Watch = class {
|
|
|
2832
3723
|
};
|
|
2833
3724
|
|
|
2834
3725
|
// src/webtty.ts
|
|
2835
|
-
var
|
|
2836
|
-
var osFamilySchema =
|
|
3726
|
+
var z10 = __toESM(require("zod"));
|
|
3727
|
+
var osFamilySchema = z10.enum([
|
|
2837
3728
|
"linux",
|
|
2838
3729
|
"macos",
|
|
2839
3730
|
"windows",
|
|
@@ -2841,45 +3732,55 @@ var osFamilySchema = z9.enum([
|
|
|
2841
3732
|
"openbsd",
|
|
2842
3733
|
"freebsd"
|
|
2843
3734
|
]);
|
|
2844
|
-
var webttyCapabilitySchema =
|
|
2845
|
-
var webttyFSModeSchema =
|
|
2846
|
-
var webttyEncryptionPolicySchema =
|
|
3735
|
+
var webttyCapabilitySchema = z10.enum(["exec", "fs"]);
|
|
3736
|
+
var webttyFSModeSchema = z10.enum(["read-write", "read-only"]);
|
|
3737
|
+
var webttyEncryptionPolicySchema = z10.enum([
|
|
2847
3738
|
"disabled",
|
|
2848
3739
|
"explicit_key",
|
|
2849
3740
|
"workspace_managed"
|
|
2850
3741
|
]);
|
|
2851
|
-
var
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
3742
|
+
var webttyTransportSchema = z10.enum([
|
|
3743
|
+
"plain",
|
|
3744
|
+
"websocket",
|
|
3745
|
+
"webtransport"
|
|
3746
|
+
]);
|
|
3747
|
+
var webttyServerSchema = z10.object({
|
|
3748
|
+
tunnel_id: z10.string(),
|
|
3749
|
+
tunnel_name: z10.string().optional(),
|
|
3750
|
+
target: z10.string().optional(),
|
|
3751
|
+
rstream_url: z10.string().optional(),
|
|
3752
|
+
status: z10.string().optional(),
|
|
3753
|
+
publish: z10.boolean().optional(),
|
|
3754
|
+
workspace_id: z10.string().optional(),
|
|
3755
|
+
project_id: z10.string().optional(),
|
|
3756
|
+
tunnel_protocol: z10.enum(["http", "webtty"]).optional(),
|
|
3757
|
+
tunnel_type: z10.enum(["bytestream", "datagram"]).optional(),
|
|
3758
|
+
http_version: z10.string().optional(),
|
|
3759
|
+
transport: webttyTransportSchema.optional(),
|
|
3760
|
+
transport_error: z10.string().optional(),
|
|
3761
|
+
managed: z10.boolean(),
|
|
3762
|
+
host: z10.string(),
|
|
3763
|
+
token_auth: z10.boolean(),
|
|
3764
|
+
server_id: z10.string().optional(),
|
|
3765
|
+
server_name: z10.string().optional(),
|
|
3766
|
+
host_key_id: z10.string().optional(),
|
|
3767
|
+
e2e: z10.enum(["disabled", "required"]).optional(),
|
|
3768
|
+
client_proof: z10.enum(["none", "required"]).optional(),
|
|
2869
3769
|
encryption_policy: webttyEncryptionPolicySchema.optional(),
|
|
2870
|
-
capabilities:
|
|
2871
|
-
exec_path:
|
|
2872
|
-
fs_path:
|
|
3770
|
+
capabilities: z10.array(webttyCapabilitySchema).optional(),
|
|
3771
|
+
exec_path: z10.string().optional(),
|
|
3772
|
+
fs_path: z10.string().optional(),
|
|
2873
3773
|
fs_mode: webttyFSModeSchema.optional(),
|
|
3774
|
+
fs_backend: z10.enum(["webdav", "webrtc"]).optional(),
|
|
2874
3775
|
os_family: osFamilySchema.optional(),
|
|
2875
|
-
arch:
|
|
2876
|
-
os_id:
|
|
2877
|
-
os_version_id:
|
|
2878
|
-
os_version_codename:
|
|
2879
|
-
os_pretty_name:
|
|
2880
|
-
kernel_release:
|
|
2881
|
-
hostname:
|
|
2882
|
-
labels:
|
|
3776
|
+
arch: z10.string().optional(),
|
|
3777
|
+
os_id: z10.string().optional(),
|
|
3778
|
+
os_version_id: z10.string().optional(),
|
|
3779
|
+
os_version_codename: z10.string().optional(),
|
|
3780
|
+
os_pretty_name: z10.string().optional(),
|
|
3781
|
+
kernel_release: z10.string().optional(),
|
|
3782
|
+
hostname: z10.string().optional(),
|
|
3783
|
+
labels: z10.record(z10.string(), z10.string().optional()).optional()
|
|
2883
3784
|
});
|
|
2884
3785
|
function isWebTTYCapability(value) {
|
|
2885
3786
|
return webttyCapabilitySchema.safeParse(value).success;
|
|
@@ -2897,11 +3798,12 @@ function webTTYCapabilitiesForLabels(labels) {
|
|
|
2897
3798
|
function webTTYHasCapability(capabilities, capability) {
|
|
2898
3799
|
return capabilities.includes(capability);
|
|
2899
3800
|
}
|
|
2900
|
-
function parser(tunnel) {
|
|
3801
|
+
function parser(tunnel, includePrivate) {
|
|
2901
3802
|
if (tunnel.status !== "online") return null;
|
|
2902
|
-
if (tunnel.publish !== true) return null;
|
|
3803
|
+
if (tunnel.publish !== true && !includePrivate) return null;
|
|
2903
3804
|
const managedProtocol = tunnel.protocol === "webtty";
|
|
2904
|
-
if (tunnel.protocol !== "http" && !managedProtocol)
|
|
3805
|
+
if (tunnel.protocol !== void 0 && tunnel.protocol !== "http" && !managedProtocol)
|
|
3806
|
+
return null;
|
|
2905
3807
|
const tunnelLabels = tunnel.labels ?? {};
|
|
2906
3808
|
if (!managedProtocol && tunnelLabels["application-protocol"] !== "rstream.webtty") {
|
|
2907
3809
|
return null;
|
|
@@ -2929,8 +3831,11 @@ function parser(tunnel) {
|
|
|
2929
3831
|
workspace_id: tunnel.workspace_id,
|
|
2930
3832
|
project_id: tunnel.project_id,
|
|
2931
3833
|
tunnel_protocol: tunnel.protocol,
|
|
3834
|
+
tunnel_type: tunnel.type,
|
|
3835
|
+
http_version: tunnel.http_version,
|
|
3836
|
+
...webTTYTransportMetadata(tunnelLabels["rstream.webtty.transport"]),
|
|
2932
3837
|
managed: managedProtocol,
|
|
2933
|
-
host: formatTunnelHost(tunnel),
|
|
3838
|
+
host: formatTunnelHost(tunnel) ?? "",
|
|
2934
3839
|
token_auth: tunnel.token_auth === true,
|
|
2935
3840
|
server_id: serverId || void 0,
|
|
2936
3841
|
server_name: serverName || void 0,
|
|
@@ -2941,6 +3846,7 @@ function parser(tunnel) {
|
|
|
2941
3846
|
capabilities,
|
|
2942
3847
|
exec_path: webTTYHasCapability(capabilities, "exec") ? tunnelLabels["rstream.webtty.exec.path"] ?? "/" : void 0,
|
|
2943
3848
|
fs_path: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.path"] ?? "/fs" : void 0,
|
|
3849
|
+
fs_backend: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.backend"] ?? "webdav" : void 0,
|
|
2944
3850
|
fs_mode: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.mode"] ?? "read-write" : void 0,
|
|
2945
3851
|
os_family: tunnelLabels["rstream.webtty.os_family"],
|
|
2946
3852
|
arch: tunnelLabels["rstream.webtty.arch"],
|
|
@@ -2956,8 +3862,54 @@ function parser(tunnel) {
|
|
|
2956
3862
|
if (!parsed.success) return null;
|
|
2957
3863
|
return parsed.data;
|
|
2958
3864
|
}
|
|
2959
|
-
function parseWebTTYServers(tunnels) {
|
|
2960
|
-
return tunnels.map((tunnel) => parser(tunnel)).filter((server) => server !== null);
|
|
3865
|
+
function parseWebTTYServers(tunnels, options = {}) {
|
|
3866
|
+
return tunnels.map((tunnel) => parser(tunnel, options.includePrivate === true)).filter((server) => server !== null);
|
|
3867
|
+
}
|
|
3868
|
+
function webTTYTransportMetadata(value) {
|
|
3869
|
+
if (value === void 0) return {};
|
|
3870
|
+
const parsed = webttyTransportSchema.safeParse(value);
|
|
3871
|
+
if (!parsed.success)
|
|
3872
|
+
return {
|
|
3873
|
+
transport_error: "Server advertises an invalid WebTTY transport."
|
|
3874
|
+
};
|
|
3875
|
+
return { transport: parsed.data };
|
|
3876
|
+
}
|
|
3877
|
+
function resolveWebTTYServerTransport(server, requested) {
|
|
3878
|
+
if (server.transport_error !== void 0)
|
|
3879
|
+
throw new Error(server.transport_error);
|
|
3880
|
+
const advertised = server.transport ?? (server.tunnel_type === "datagram" ? "webtransport" : void 0);
|
|
3881
|
+
if (advertised !== void 0 && server.tunnel_type !== void 0 && advertised === "webtransport" !== (server.tunnel_type === "datagram")) {
|
|
3882
|
+
throw new Error(
|
|
3883
|
+
`Server WebTTY transport ${advertised} conflicts with tunnel type ${server.tunnel_type}.`
|
|
3884
|
+
);
|
|
3885
|
+
}
|
|
3886
|
+
if (advertised === "webtransport" && server.http_version !== void 0 && server.http_version !== "h3") {
|
|
3887
|
+
throw new Error("Server WebTransport requires HTTP/3.");
|
|
3888
|
+
}
|
|
3889
|
+
if (requested !== void 0 && advertised !== void 0 && requested !== advertised) {
|
|
3890
|
+
throw new Error(
|
|
3891
|
+
`Requested WebTTY transport ${requested} conflicts with server transport ${advertised}.`
|
|
3892
|
+
);
|
|
3893
|
+
}
|
|
3894
|
+
return advertised ?? requested ?? "websocket";
|
|
3895
|
+
}
|
|
3896
|
+
function resolveWebTTYBrowserEndpoint(server) {
|
|
3897
|
+
const transport = resolveWebTTYServerTransport(server);
|
|
3898
|
+
if (transport === "plain")
|
|
3899
|
+
throw new Error(
|
|
3900
|
+
"Plain WebTTY requires a native client; this server cannot be opened in a browser."
|
|
3901
|
+
);
|
|
3902
|
+
if (server.publish === false || server.host === "")
|
|
3903
|
+
throw new Error(
|
|
3904
|
+
"Private WebTTY requires a native client and its rstrm:// address."
|
|
3905
|
+
);
|
|
3906
|
+
const url = new URL(
|
|
3907
|
+
`${transport === "webtransport" ? "https" : "wss"}://${server.host}`
|
|
3908
|
+
);
|
|
3909
|
+
const execPath = server.exec_path?.trim();
|
|
3910
|
+
if (execPath)
|
|
3911
|
+
url.pathname = execPath.startsWith("/") ? execPath : `/${execPath}`;
|
|
3912
|
+
return { url: url.toString(), transport };
|
|
2961
3913
|
}
|
|
2962
3914
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2963
3915
|
0 && (module.exports = {
|
|
@@ -3015,7 +3967,9 @@ function parseWebTTYServers(tunnels) {
|
|
|
3015
3967
|
resolveTunnelsCredentials,
|
|
3016
3968
|
resolveTunnelsEngine,
|
|
3017
3969
|
resolveTunnelsRegion,
|
|
3970
|
+
resolveWebTTYBrowserEndpoint,
|
|
3018
3971
|
resolveWebTTYControlRequestParamsSchema,
|
|
3972
|
+
resolveWebTTYServerTransport,
|
|
3019
3973
|
signWebhookPayload,
|
|
3020
3974
|
streamEndpointSchema,
|
|
3021
3975
|
streamEntrySchema,
|
|
@@ -3025,6 +3979,7 @@ function parseWebTTYServers(tunnels) {
|
|
|
3025
3979
|
streamWebTTYMetadataSchema,
|
|
3026
3980
|
tunnelFilterSchema,
|
|
3027
3981
|
turnCredentialModeSchema,
|
|
3982
|
+
validateTURNCredentialsRequest,
|
|
3028
3983
|
webTTYCapabilitiesSchema,
|
|
3029
3984
|
webTTYContextMetadataSchema,
|
|
3030
3985
|
webTTYControlRequestSchema,
|
|
@@ -3045,5 +4000,6 @@ function parseWebTTYServers(tunnels) {
|
|
|
3045
4000
|
webTTYTransportSchema,
|
|
3046
4001
|
webhookEventsSchema,
|
|
3047
4002
|
webttyServerSchema,
|
|
4003
|
+
webttyTransportSchema,
|
|
3048
4004
|
wsEventsSchema
|
|
3049
4005
|
});
|