@mh-gg/runtime-config 0.1.1-alpha.20260813T095547173Z
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/LICENSE +21 -0
- package/README.md +4 -0
- package/package.json +23 -0
- package/src/index.cjs +714 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matterhorn contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mh-gg/runtime-config",
|
|
3
|
+
"version": "0.1.1-alpha.20260813T095547173Z",
|
|
4
|
+
"description": "Shared runtime, relay, signaling, and ICE configuration for Matterhorn.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "src/index.cjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.cjs"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22.12"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"README.md",
|
|
17
|
+
"package.json"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test test/*.test.cjs",
|
|
21
|
+
"coverage": "node --test --experimental-test-coverage --test-coverage-lines=80 --test-coverage-functions=80 --test-coverage-branches=80 --test-coverage-include=src/*.cjs test/*.test.cjs"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.cjs
ADDED
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const PROTOCOL = 1;
|
|
3
|
+
const DEFAULT_RELAY_MAX_EVENTS = 10000;
|
|
4
|
+
const DEFAULT_RELAY_MAX_EVENTS_PER_ROOM = 2500;
|
|
5
|
+
const DEFAULT_RELAY_MAX_BYTES_PER_ROOM = 8 * 1024 * 1024;
|
|
6
|
+
const DEFAULT_RELAY_MAX_BYTES_TOTAL = 256 * 1024 * 1024;
|
|
7
|
+
const DEFAULT_RELAY_MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
8
|
+
const DEFAULT_RELAY_MAX_FILE_BYTES_PER_ROOM = 1024 * 1024 * 1024;
|
|
9
|
+
const DEFAULT_RELAY_MAX_FILE_BYTES_TOTAL = 10 * 1024 * 1024 * 1024;
|
|
10
|
+
const DEFAULT_RELAY_FILE_ROOM_POLICY = "allow-all";
|
|
11
|
+
const DEFAULT_RELAY_MAX_EVENTS_PER_PUBKEY_WINDOW = 240;
|
|
12
|
+
const DEFAULT_RELAY_PUBKEY_QUOTA_WINDOW_SECONDS = 10 * 60;
|
|
13
|
+
const DEFAULT_RELAY_MAX_EVENT_BYTES = 8 * 1024;
|
|
14
|
+
const DEFAULT_RELAY_MAX_NIP44_EVENT_BYTES = 60 * 1024;
|
|
15
|
+
const DEFAULT_RELAY_MAX_FUTURE_SECONDS = 10 * 60;
|
|
16
|
+
const DEFAULT_RELAY_ACTIVE_FANOUT = 4;
|
|
17
|
+
const DEFAULT_RELAY_SFU_MAX_PARTICIPANTS = 16;
|
|
18
|
+
const DEFAULT_RELAY_MAX_IPC_MESSAGE_BYTES = 256 * 1024;
|
|
19
|
+
const DEFAULT_RELAY_MAX_ROOM_MESSAGE_BYTES = 64 * 1024;
|
|
20
|
+
const DEFAULT_RELAY_MAX_MESH_MESSAGE_BYTES = 4 * 1024 * 1024;
|
|
21
|
+
const DEFAULT_RELAY_MAX_NOSTR_MESSAGE_BYTES = 64 * 1024;
|
|
22
|
+
const DEFAULT_RELAY_MAX_CALL_SIGNAL_BYTES = 32 * 1024;
|
|
23
|
+
const DEFAULT_RELAY_RATE_WINDOW_MS = 10 * 1000;
|
|
24
|
+
const DEFAULT_RELAY_MAX_UNAUTHENTICATED_MESSAGES_PER_WINDOW = 20;
|
|
25
|
+
const DEFAULT_RELAY_MAX_ROOM_MESSAGES_PER_WINDOW = 120;
|
|
26
|
+
const DEFAULT_RELAY_MAX_MESH_MESSAGES_PER_WINDOW = 240;
|
|
27
|
+
const DEFAULT_RELAY_MAX_IPC_MESSAGES_PER_WINDOW = 240;
|
|
28
|
+
const DEFAULT_RELAY_HEARTBEAT_MS = 1000;
|
|
29
|
+
const DEFAULT_RELAY_HEARTBEAT_TIMEOUT_MS = 2500;
|
|
30
|
+
const DEFAULT_RELAY_FAILOVER_MS = 3500;
|
|
31
|
+
const DEFAULT_RELAY_EDGE_MAX_QUEUED_FRAMES_PER_CONNECTION = 1000;
|
|
32
|
+
const DEFAULT_RELAY_EDGE_MAX_QUEUED_BYTES_PER_CONNECTION = 8 * 1024 * 1024;
|
|
33
|
+
const DEFAULT_RELAY_EDGE_MAX_QUEUED_BYTES_TOTAL = 128 * 1024 * 1024;
|
|
34
|
+
const DEFAULT_RELAY_EDGE_QUEUE_BACKPRESSURE_RATIO = 0.8;
|
|
35
|
+
const DEFAULT_RELAY_EDGE_MAX_MIGRATION_DURATION_MS = 30 * 1000;
|
|
36
|
+
const RELAY_TRANSPORT_MODE_STANDALONE = "standalone";
|
|
37
|
+
const RELAY_TRANSPORT_MODE_EDGE = "edge";
|
|
38
|
+
const RELAY_TRANSPORT_MODE_EDGE_WORKER = "edge-worker";
|
|
39
|
+
const RELAY_TRANSPORT_MODES = new Set([
|
|
40
|
+
RELAY_TRANSPORT_MODE_STANDALONE,
|
|
41
|
+
RELAY_TRANSPORT_MODE_EDGE,
|
|
42
|
+
RELAY_TRANSPORT_MODE_EDGE_WORKER
|
|
43
|
+
]);
|
|
44
|
+
const MATTERHORN_PROFILE_DEVELOPMENT = "development";
|
|
45
|
+
const MATTERHORN_PROFILE_ALPHA = "alpha";
|
|
46
|
+
const MATTERHORN_PROFILE_PRODUCTION = "production";
|
|
47
|
+
const MATTERHORN_PROFILES = new Set([
|
|
48
|
+
MATTERHORN_PROFILE_DEVELOPMENT,
|
|
49
|
+
MATTERHORN_PROFILE_ALPHA,
|
|
50
|
+
MATTERHORN_PROFILE_PRODUCTION
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function requireCliValue(argv, index, token) {
|
|
56
|
+
const value = argv[index + 1];
|
|
57
|
+
if (value === undefined || String(value).startsWith("--")) {
|
|
58
|
+
throw new Error(`${token} requires a value.`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function requireFiniteNumber(value, name, { integer = false, min = 0, max = Number.MAX_SAFE_INTEGER, optional = false } = {}) {
|
|
64
|
+
if (optional && value === undefined) return undefined;
|
|
65
|
+
if (!Number.isFinite(value)) throw new Error(`${name} must be a finite number.`);
|
|
66
|
+
if (integer && !Number.isInteger(value)) throw new Error(`${name} must be an integer.`);
|
|
67
|
+
if (value < min || value > max) throw new Error(`${name} must be between ${min} and ${max}.`);
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function validateRelayNumericOptions(args) {
|
|
72
|
+
const integerNonNegative = [
|
|
73
|
+
"ipcPort", "activeRelayFanout", "relayHeartbeatMs", "relayHeartbeatTimeoutMs", "relayFailoverMs",
|
|
74
|
+
"maxEvents", "maxEventsPerRoom", "maxBytesPerRoom", "maxBytesTotal", "maxFileBytes",
|
|
75
|
+
"maxFileBytesPerRoom", "maxFileBytesTotal", "maxEventsPerPubkeyWindow",
|
|
76
|
+
"pubkeyQuotaWindowSeconds", "maxEventBytes", "maxFutureSeconds", "sfuMaxParticipants",
|
|
77
|
+
"edgeMaxQueuedFramesPerConnection", "edgeMaxQueuedBytesPerConnection", "edgeMaxQueuedBytesTotal",
|
|
78
|
+
"edgeMaxMigrationDurationMs", "edgeRolloutReadyTimeoutMs", "edgeRolloutCanaryTimeoutMs",
|
|
79
|
+
"edgeRolloutFlushTimeoutMs", "edgeRolloutActivationTimeoutMs", "edgePostActivationBakeMs"
|
|
80
|
+
];
|
|
81
|
+
for (const key of integerNonNegative) {
|
|
82
|
+
if (args[key] !== undefined) requireFiniteNumber(args[key], key, { integer: true, min: key === "ipcPort" ? 1 : 0, max: key === "ipcPort" ? 65535 : Number.MAX_SAFE_INTEGER });
|
|
83
|
+
}
|
|
84
|
+
if (args.quotaWarningRatio !== undefined) requireFiniteNumber(args.quotaWarningRatio, "quotaWarningRatio", { min: 0, max: 1 });
|
|
85
|
+
requireFiniteNumber(args.edgeQueueBackpressureRatio, "edgeQueueBackpressureRatio", { min: 0, max: 1 });
|
|
86
|
+
if (args.relayHeartbeatTimeoutMs < args.relayHeartbeatMs) {
|
|
87
|
+
throw new Error("relayHeartbeatTimeoutMs must be greater than or equal to relayHeartbeatMs.");
|
|
88
|
+
}
|
|
89
|
+
if (args.maxEventsPerRoom > args.maxEvents) {
|
|
90
|
+
throw new Error("maxEventsPerRoom cannot exceed maxEvents.");
|
|
91
|
+
}
|
|
92
|
+
if (args.maxBytesPerRoom > args.maxBytesTotal) {
|
|
93
|
+
throw new Error("maxBytesPerRoom cannot exceed maxBytesTotal.");
|
|
94
|
+
}
|
|
95
|
+
if (args.maxFileBytes > args.maxFileBytesPerRoom) {
|
|
96
|
+
throw new Error("maxFileBytes cannot exceed maxFileBytesPerRoom.");
|
|
97
|
+
}
|
|
98
|
+
if (args.maxFileBytesPerRoom > args.maxFileBytesTotal) {
|
|
99
|
+
throw new Error("maxFileBytesPerRoom cannot exceed maxFileBytesTotal.");
|
|
100
|
+
}
|
|
101
|
+
if (!["allow-all", "allow-list"].includes(args.fileRoomPolicy)) {
|
|
102
|
+
throw new Error("fileRoomPolicy must be allow-all or allow-list.");
|
|
103
|
+
}
|
|
104
|
+
if (args.edgeMaxQueuedBytesPerConnection > args.edgeMaxQueuedBytesTotal) {
|
|
105
|
+
throw new Error("edgeMaxQueuedBytesPerConnection cannot exceed edgeMaxQueuedBytesTotal.");
|
|
106
|
+
}
|
|
107
|
+
return args;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseRelayPeers(value) {
|
|
111
|
+
return String(value || "")
|
|
112
|
+
.split(",")
|
|
113
|
+
.map((item) => item.trim())
|
|
114
|
+
.filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseIceServers(value) {
|
|
118
|
+
if (Array.isArray(value)) return value.map(normalizeIceServer).filter(Boolean);
|
|
119
|
+
return String(value || "")
|
|
120
|
+
.split(",")
|
|
121
|
+
.map((item) => item.trim())
|
|
122
|
+
.filter(Boolean)
|
|
123
|
+
.map((urls) => ({ urls: normalizeIceServerUrl(urls, "ICE server") }));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function normalizeIceServerUrl(value, name) {
|
|
127
|
+
const url = String(value || "").trim();
|
|
128
|
+
if (!/^(stun|stuns|turn|turns):[^,\s]+$/i.test(url)) {
|
|
129
|
+
throw new Error(`Invalid ${name} URL ${JSON.stringify(value)}.`);
|
|
130
|
+
}
|
|
131
|
+
return url;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function normalizeIceServer(server) {
|
|
135
|
+
if (!server) return undefined;
|
|
136
|
+
if (typeof server === "string") return { urls: normalizeIceServerUrl(server, "ICE server") };
|
|
137
|
+
if (typeof server !== "object") throw new Error(`Invalid ICE server ${JSON.stringify(server)}.`);
|
|
138
|
+
const urls = Array.isArray(server.urls)
|
|
139
|
+
? server.urls.map((url) => normalizeIceServerUrl(url, "ICE server"))
|
|
140
|
+
: normalizeIceServerUrl(server.urls, "ICE server");
|
|
141
|
+
const normalized = { urls };
|
|
142
|
+
if (typeof server.username === "string") normalized.username = server.username;
|
|
143
|
+
if (Object.prototype.hasOwnProperty.call(server, "credential")) normalized.credential = server.credential;
|
|
144
|
+
return normalized;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function iceServerUrls(servers) {
|
|
148
|
+
return servers.flatMap((server) => Array.isArray(server.urls) ? server.urls : [server.urls]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function redactIceServers(servers) {
|
|
152
|
+
return parseIceServers(servers).map((server) => {
|
|
153
|
+
const redacted = { ...server };
|
|
154
|
+
if (Object.prototype.hasOwnProperty.call(redacted, "credential")) redacted.credential = "<redacted>";
|
|
155
|
+
return redacted;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function normalizeTurnCredential(value, name) {
|
|
160
|
+
const text = String(value || "").trim();
|
|
161
|
+
if (!text) throw new Error(`${name} is required when MATTERHORN_TURN_URLS is set.`);
|
|
162
|
+
return text;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseTurnIceServers(env = process.env) {
|
|
166
|
+
const urls = String(env.MATTERHORN_TURN_URLS || "")
|
|
167
|
+
.split(",")
|
|
168
|
+
.map((item) => item.trim())
|
|
169
|
+
.filter(Boolean);
|
|
170
|
+
if (!urls.length) return [];
|
|
171
|
+
const username = normalizeTurnCredential(env.MATTERHORN_TURN_USERNAME, "MATTERHORN_TURN_USERNAME");
|
|
172
|
+
const credential = normalizeTurnCredential(env.MATTERHORN_TURN_CREDENTIAL, "MATTERHORN_TURN_CREDENTIAL");
|
|
173
|
+
return [{
|
|
174
|
+
urls: urls.map((entry) => {
|
|
175
|
+
const url = normalizeIceServerUrl(entry, "MATTERHORN_TURN_URLS");
|
|
176
|
+
if (!/^turns?:/i.test(url)) throw new Error(`Invalid MATTERHORN_TURN_URLS entry ${JSON.stringify(entry)}.`);
|
|
177
|
+
return url;
|
|
178
|
+
}),
|
|
179
|
+
username,
|
|
180
|
+
credential
|
|
181
|
+
}];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function normalizeIceMode(value) {
|
|
185
|
+
const mode = String(value || "").trim().toLowerCase();
|
|
186
|
+
if (!mode) return undefined;
|
|
187
|
+
if (["local", "external", "none"].includes(mode)) return mode;
|
|
188
|
+
throw new Error("MATTERHORN_ICE_MODE must be local, external, or none.");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function parseExternalIceServersJson(value) {
|
|
192
|
+
if (!String(value || "").trim()) return [];
|
|
193
|
+
let parsed;
|
|
194
|
+
try {
|
|
195
|
+
parsed = JSON.parse(value);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw new Error(`MATTERHORN_EXTERNAL_ICE_SERVERS_JSON is not valid JSON: ${error.message}`);
|
|
198
|
+
}
|
|
199
|
+
if (!Array.isArray(parsed)) throw new Error("MATTERHORN_EXTERNAL_ICE_SERVERS_JSON must be an array.");
|
|
200
|
+
return parsed.map(normalizeIceServer).filter(Boolean);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function localTurnIceServers(env = process.env) {
|
|
204
|
+
const turnHost = String(env.MATTERHORN_TURN_HOST || env.MATTERHORN_TURN_REALM || "").trim();
|
|
205
|
+
const turnPort = String(env.MATTERHORN_TURN_PORT || "3478").trim();
|
|
206
|
+
const turnsPort = String(env.MATTERHORN_TURNS_PORT || "5349").trim();
|
|
207
|
+
const turnsEnabled = parseBool(env.MATTERHORN_TURNS_ENABLED);
|
|
208
|
+
const stun = turnHost ? [{ urls: normalizeIceServerUrl(`stun:${turnHost}:${turnPort}`, "MATTERHORN_TURN_HOST") }] : [];
|
|
209
|
+
const explicitTurn = parseTurnIceServers(env);
|
|
210
|
+
if (explicitTurn.length || !turnHost) return [...stun, ...explicitTurn];
|
|
211
|
+
const username = normalizeTurnCredential(env.MATTERHORN_TURN_USERNAME, "MATTERHORN_TURN_USERNAME");
|
|
212
|
+
const credential = normalizeTurnCredential(env.MATTERHORN_TURN_CREDENTIAL, "MATTERHORN_TURN_CREDENTIAL");
|
|
213
|
+
const urls = [
|
|
214
|
+
normalizeIceServerUrl(`turn:${turnHost}:${turnPort}?transport=udp`, "MATTERHORN_TURN_HOST"),
|
|
215
|
+
normalizeIceServerUrl(`turn:${turnHost}:${turnPort}?transport=tcp`, "MATTERHORN_TURN_HOST")
|
|
216
|
+
];
|
|
217
|
+
if (turnsEnabled) urls.push(normalizeIceServerUrl(`turns:${turnHost}:${turnsPort}?transport=tcp`, "MATTERHORN_TURN_HOST"));
|
|
218
|
+
return [...stun, { urls, username, credential }];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function configuredIceServers(env = process.env, fallback = "stun:stun.l.google.com:19302") {
|
|
222
|
+
const mode = normalizeIceMode(env.MATTERHORN_ICE_MODE);
|
|
223
|
+
if (mode === "none") return [];
|
|
224
|
+
if (mode === "external") {
|
|
225
|
+
const external = parseExternalIceServersJson(env.MATTERHORN_EXTERNAL_ICE_SERVERS_JSON);
|
|
226
|
+
if (external.length) return external;
|
|
227
|
+
return [...parseIceServers(env.ICE_SERVERS || ""), ...parseTurnIceServers(env)];
|
|
228
|
+
}
|
|
229
|
+
if (mode === "local") {
|
|
230
|
+
return [...parseIceServers(env.ICE_SERVERS || ""), ...localTurnIceServers(env)];
|
|
231
|
+
}
|
|
232
|
+
const configured = parseIceServers(env.ICE_SERVERS || fallback);
|
|
233
|
+
return [...configured, ...parseTurnIceServers(env)];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function parseBool(value) {
|
|
237
|
+
return ["1", "true", "yes", "on"].includes(String(value || "").trim().toLowerCase());
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isMatterhornProfile(value) {
|
|
241
|
+
return MATTERHORN_PROFILES.has(value);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function defaultMatterhornProfile(env = process.env) {
|
|
245
|
+
return env.NODE_ENV === "production" ? MATTERHORN_PROFILE_PRODUCTION : MATTERHORN_PROFILE_DEVELOPMENT;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function normalizeMatterhornProfile(value, fallback = MATTERHORN_PROFILE_DEVELOPMENT) {
|
|
249
|
+
const profile = String(value || fallback || "").trim().toLowerCase();
|
|
250
|
+
if (!isMatterhornProfile(profile)) throw new Error(`Invalid Matterhorn profile ${JSON.stringify(value)}.`);
|
|
251
|
+
return profile;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function defaultRuntimeDataDir() {
|
|
255
|
+
return path.resolve(".matterhorn");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function defaultRelayStoragePath(dataDir) {
|
|
259
|
+
return path.join(dataDir, "relay");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function resolveStoragePath(value, fallback) {
|
|
263
|
+
const raw = String(value || fallback || "");
|
|
264
|
+
if (raw === "~") return require("node:os").homedir();
|
|
265
|
+
if (raw.startsWith("~/") || raw.startsWith("~\\")) {
|
|
266
|
+
return path.join(require("node:os").homedir(), raw.slice(2));
|
|
267
|
+
}
|
|
268
|
+
return path.resolve(raw);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parseRelayNumeric(value, fallback) {
|
|
272
|
+
const parsed = Number(value);
|
|
273
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function normalizeRelayTransportMode(value) {
|
|
277
|
+
const mode = String(value || "").trim();
|
|
278
|
+
if (!mode) return undefined;
|
|
279
|
+
if (RELAY_TRANSPORT_MODES.has(mode)) return mode;
|
|
280
|
+
throw new Error(`Invalid relay transport mode ${JSON.stringify(value)}.`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function resolveRelayTransportMode(input = {}, options = {}) {
|
|
284
|
+
const edgeSocket = String(input.edgeSocket || "").trim();
|
|
285
|
+
const mode = normalizeRelayTransportMode(input.transportMode)
|
|
286
|
+
|| (edgeSocket ? RELAY_TRANSPORT_MODE_EDGE_WORKER : RELAY_TRANSPORT_MODE_STANDALONE);
|
|
287
|
+
|
|
288
|
+
if (mode === RELAY_TRANSPORT_MODE_EDGE) {
|
|
289
|
+
if (!options.allowEdgeMode) throw new Error("Relay edge mode must be started with matterhorn relay edge start, not matterhorn relay start.");
|
|
290
|
+
if (!edgeSocket) throw new Error("edge transport mode requires --socket or MATTERHORN_RELAY_EDGE_SOCKET.");
|
|
291
|
+
return mode;
|
|
292
|
+
}
|
|
293
|
+
if (mode === RELAY_TRANSPORT_MODE_EDGE_WORKER && !edgeSocket) {
|
|
294
|
+
throw new Error("edge-worker transport mode requires --edge-socket or MATTERHORN_RELAY_EDGE_SOCKET.");
|
|
295
|
+
}
|
|
296
|
+
if (mode === RELAY_TRANSPORT_MODE_STANDALONE && edgeSocket) {
|
|
297
|
+
throw new Error("Configured edge socket requires edge-worker transport mode; refusing to start standalone with edge-worker configuration.");
|
|
298
|
+
}
|
|
299
|
+
return mode;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function parseRelayArgsFromEnv(env, options = {}) {
|
|
303
|
+
const defaultStoragePath = options.defaultStoragePath || defaultRelayStoragePath(options.defaultDataDir || defaultRuntimeDataDir());
|
|
304
|
+
const iceServerConfigs = configuredIceServers(env);
|
|
305
|
+
return {
|
|
306
|
+
matterhornProfile: normalizeMatterhornProfile(env.MATTERHORN_PROFILE, defaultMatterhornProfile(env)),
|
|
307
|
+
ipcHost: env.MATTERHORN_RELAY_IPC_HOST || options.defaultIpcHost || "127.0.0.1",
|
|
308
|
+
ipcPort: parseRelayNumeric(env.MATTERHORN_RELAY_IPC_PORT, options.defaultIpcPort || 42777),
|
|
309
|
+
storagePath: env.MATTERHORN_RELAY_STORAGE || defaultStoragePath,
|
|
310
|
+
relayPeerId: env.MATTERHORN_RELAY_PEER_ID || undefined,
|
|
311
|
+
relayName: env.MATTERHORN_RELAY_NAME || undefined,
|
|
312
|
+
relayDomain: env.MATTERHORN_RELAY_DOMAIN || undefined,
|
|
313
|
+
relayLauncherUrl: env.MATTERHORN_RELAY_LAUNCHER_URL || undefined,
|
|
314
|
+
relayVaultUrl: env.MATTERHORN_RELAY_VAULT_URL || undefined,
|
|
315
|
+
relayAppHostUrl: env.MATTERHORN_RELAY_APP_HOST_URL || undefined,
|
|
316
|
+
relayRoomWebSocketUrl: env.MATTERHORN_RELAY_ROOM_WEBSOCKET_URL || undefined,
|
|
317
|
+
oauthEnabled: parseBool(env.MATTERHORN_OAUTH_ENABLED),
|
|
318
|
+
oauthConfigPath: env.MATTERHORN_OAUTH_CONFIG || undefined,
|
|
319
|
+
oauthDeliveryOrigin: env.MATTERHORN_OAUTH_DELIVERY_ORIGIN || undefined,
|
|
320
|
+
relayPeers: parseRelayPeers(env.MATTERHORN_RELAY_PEERS),
|
|
321
|
+
relayRefs: parseRelayPeers(env.MATTERHORN_RELAY_REFS),
|
|
322
|
+
nondiscoverable: parseBool(env.MATTERHORN_RELAY_NONDISCOVERABLE),
|
|
323
|
+
activeRelayFanout: parseRelayNumeric(env.MATTERHORN_RELAY_ACTIVE_FANOUT, DEFAULT_RELAY_ACTIVE_FANOUT),
|
|
324
|
+
relayHeartbeatMs: parseRelayNumeric(env.MATTERHORN_RELAY_HEARTBEAT_MS, DEFAULT_RELAY_HEARTBEAT_MS),
|
|
325
|
+
relayHeartbeatTimeoutMs: parseRelayNumeric(env.MATTERHORN_RELAY_HEARTBEAT_TIMEOUT_MS, DEFAULT_RELAY_HEARTBEAT_TIMEOUT_MS),
|
|
326
|
+
relayFailoverMs: parseRelayNumeric(env.MATTERHORN_RELAY_FAILOVER_MS, DEFAULT_RELAY_FAILOVER_MS),
|
|
327
|
+
maxEvents: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_EVENTS, DEFAULT_RELAY_MAX_EVENTS),
|
|
328
|
+
maxEventsPerRoom: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_EVENTS_PER_ROOM, DEFAULT_RELAY_MAX_EVENTS_PER_ROOM),
|
|
329
|
+
maxBytesPerRoom: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_BYTES_PER_ROOM, DEFAULT_RELAY_MAX_BYTES_PER_ROOM),
|
|
330
|
+
maxBytesTotal: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_BYTES_TOTAL, DEFAULT_RELAY_MAX_BYTES_TOTAL),
|
|
331
|
+
maxFileBytes: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_FILE_BYTES, DEFAULT_RELAY_MAX_FILE_BYTES),
|
|
332
|
+
maxFileBytesPerRoom: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_FILE_BYTES_PER_ROOM, DEFAULT_RELAY_MAX_FILE_BYTES_PER_ROOM),
|
|
333
|
+
maxFileBytesTotal: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_FILE_BYTES_TOTAL, DEFAULT_RELAY_MAX_FILE_BYTES_TOTAL),
|
|
334
|
+
fileRoomPolicy: String(env.MATTERHORN_RELAY_FILE_ROOM_POLICY || DEFAULT_RELAY_FILE_ROOM_POLICY).trim().toLowerCase(),
|
|
335
|
+
fileAllowedRooms: parseRelayPeers(env.MATTERHORN_RELAY_FILE_ALLOWED_ROOMS),
|
|
336
|
+
fileDeniedRooms: parseRelayPeers(env.MATTERHORN_RELAY_FILE_DENIED_ROOMS),
|
|
337
|
+
quotaWarningRatio: parseRelayNumeric(env.MATTERHORN_RELAY_QUOTA_WARNING_RATIO, undefined),
|
|
338
|
+
relayTrustMode: env.MATTERHORN_RELAY_TRUST_MODE || undefined,
|
|
339
|
+
transportMode: env.MATTERHORN_RELAY_TRANSPORT_MODE || undefined,
|
|
340
|
+
edgeSocket: env.MATTERHORN_RELAY_EDGE_SOCKET || undefined,
|
|
341
|
+
edgeWorkerId: env.MATTERHORN_RELAY_EDGE_WORKER_ID || undefined,
|
|
342
|
+
edgeWorkerSecretFile: env.MATTERHORN_RELAY_EDGE_WORKER_SECRET_FILE || undefined,
|
|
343
|
+
allowInsecureWorkerAuth: parseBool(env.MATTERHORN_RELAY_ALLOW_INSECURE_WORKER_AUTH),
|
|
344
|
+
relayPackageName: env.MATTERHORN_RELAY_PACKAGE_NAME || undefined,
|
|
345
|
+
relayPackageTag: env.MATTERHORN_RELAY_PACKAGE_TAG || undefined,
|
|
346
|
+
relayPackageVersion: env.MATTERHORN_RELAY_PACKAGE_VERSION || undefined,
|
|
347
|
+
relayWorkerImage: env.MATTERHORN_RELAY_WORKER_IMAGE || undefined,
|
|
348
|
+
relayWorkerImageMode: env.MATTERHORN_RELAY_WORKER_IMAGE_MODE || undefined,
|
|
349
|
+
relayWorkerImagePrefix: env.MATTERHORN_RELAY_WORKER_IMAGE_PREFIX || undefined,
|
|
350
|
+
relayWorkerImageBuildContext: env.MATTERHORN_RELAY_WORKER_IMAGE_BUILD_CONTEXT || undefined,
|
|
351
|
+
relayWorkerCliPackageName: env.MATTERHORN_RELAY_WORKER_CLI_PACKAGE_NAME || undefined,
|
|
352
|
+
relayWorkerController: env.MATTERHORN_RELAY_WORKER_CONTROLLER || undefined,
|
|
353
|
+
relayWorkerComposeFile: env.MATTERHORN_RELAY_COMPOSE_FILE || undefined,
|
|
354
|
+
relayWorkerComposeProjectDirectory: env.MATTERHORN_RELAY_COMPOSE_PROJECT_DIRECTORY || undefined,
|
|
355
|
+
relayWorkerComposeProjectName: env.MATTERHORN_RELAY_COMPOSE_PROJECT_NAME || undefined,
|
|
356
|
+
relayReleaseStorageClass: env.MATTERHORN_RELAY_RELEASE_STORAGE_CLASS || env.MATTERHORN_RELAY_STORAGE_MIGRATION_CLASS || undefined,
|
|
357
|
+
relayAllowStorageBreakingRollout: parseBool(env.MATTERHORN_RELAY_ALLOW_STORAGE_BREAKING_ROLLOUT),
|
|
358
|
+
relayStorageBackupDir: env.MATTERHORN_RELAY_STORAGE_BACKUP_DIR || undefined,
|
|
359
|
+
edgeRolloutStatePath: env.MATTERHORN_RELAY_ROLLOUT_STATE || undefined,
|
|
360
|
+
edgeRolloutLockPath: env.MATTERHORN_RELAY_ROLLOUT_LOCK || undefined,
|
|
361
|
+
edgeRolloutReadyTimeoutMs: parseRelayNumeric(env.MATTERHORN_RELAY_ROLLOUT_READY_TIMEOUT_MS, undefined),
|
|
362
|
+
edgeRolloutCanaryTimeoutMs: parseRelayNumeric(env.MATTERHORN_RELAY_ROLLOUT_CANARY_TIMEOUT_MS, undefined),
|
|
363
|
+
edgeRolloutFlushTimeoutMs: parseRelayNumeric(env.MATTERHORN_RELAY_ROLLOUT_FLUSH_TIMEOUT_MS, undefined),
|
|
364
|
+
edgeRolloutActivationTimeoutMs: parseRelayNumeric(env.MATTERHORN_RELAY_ROLLOUT_ACTIVATION_TIMEOUT_MS, undefined),
|
|
365
|
+
edgePostActivationBakeMs: parseRelayNumeric(env.MATTERHORN_RELAY_EDGE_POST_ACTIVATION_BAKE_MS || env.MATTERHORN_RELAY_ROLLOUT_BAKE_MS, undefined),
|
|
366
|
+
edgeQueuePath: env.MATTERHORN_RELAY_EDGE_QUEUE || env.MATTERHORN_RELAY_EDGE_QUEUE_PATH || undefined,
|
|
367
|
+
edgeMaxQueuedFramesPerConnection: parseRelayNumeric(env.MATTERHORN_RELAY_EDGE_MAX_QUEUED_FRAMES_PER_CONNECTION, DEFAULT_RELAY_EDGE_MAX_QUEUED_FRAMES_PER_CONNECTION),
|
|
368
|
+
edgeMaxQueuedBytesPerConnection: parseRelayNumeric(env.MATTERHORN_RELAY_EDGE_MAX_QUEUED_BYTES_PER_CONNECTION, DEFAULT_RELAY_EDGE_MAX_QUEUED_BYTES_PER_CONNECTION),
|
|
369
|
+
edgeMaxQueuedBytesTotal: parseRelayNumeric(env.MATTERHORN_RELAY_EDGE_MAX_QUEUED_BYTES_TOTAL, DEFAULT_RELAY_EDGE_MAX_QUEUED_BYTES_TOTAL),
|
|
370
|
+
edgeQueueBackpressureRatio: parseRelayNumeric(env.MATTERHORN_RELAY_EDGE_QUEUE_BACKPRESSURE_RATIO, DEFAULT_RELAY_EDGE_QUEUE_BACKPRESSURE_RATIO),
|
|
371
|
+
edgeMaxMigrationDurationMs: parseRelayNumeric(env.MATTERHORN_RELAY_EDGE_MAX_MIGRATION_DURATION_MS, DEFAULT_RELAY_EDGE_MAX_MIGRATION_DURATION_MS),
|
|
372
|
+
maxEventsPerPubkeyWindow: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_EVENTS_PER_PUBKEY_WINDOW, DEFAULT_RELAY_MAX_EVENTS_PER_PUBKEY_WINDOW),
|
|
373
|
+
pubkeyQuotaWindowSeconds: parseRelayNumeric(env.MATTERHORN_RELAY_PUBKEY_QUOTA_WINDOW_SECONDS, DEFAULT_RELAY_PUBKEY_QUOTA_WINDOW_SECONDS),
|
|
374
|
+
maxEventBytes: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_EVENT_BYTES, DEFAULT_RELAY_MAX_EVENT_BYTES),
|
|
375
|
+
maxFutureSeconds: parseRelayNumeric(env.MATTERHORN_RELAY_MAX_FUTURE_SECONDS, DEFAULT_RELAY_MAX_FUTURE_SECONDS),
|
|
376
|
+
sfuEnabled: parseBool(env.MATTERHORN_RELAY_SFU),
|
|
377
|
+
sfuPeerId: env.MATTERHORN_RELAY_SFU_PEER_ID || undefined,
|
|
378
|
+
sfuMaxParticipants: parseRelayNumeric(env.MATTERHORN_RELAY_SFU_MAX_PARTICIPANTS, DEFAULT_RELAY_SFU_MAX_PARTICIPANTS),
|
|
379
|
+
iceServers: iceServerUrls(iceServerConfigs).join(","),
|
|
380
|
+
iceServerConfigs,
|
|
381
|
+
localPeerjs: parseBool(env.MATTERHORN_RELAY_PEERJS_LOCAL)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function applyRelayCliArg(args, token, index, argv, warn) {
|
|
386
|
+
const key = token.slice(2);
|
|
387
|
+
const value = argv[index + 1];
|
|
388
|
+
const valueOptions = new Set([
|
|
389
|
+
"host", "ipc-host", "port", "ipc-port", "data-dir", "storage", "profile", "matterhorn-profile",
|
|
390
|
+
"relay-peer-id", "name", "relay-name", "relay-domain", "relay-launcher-url", "relay-vault-url", "relay-app-host-url", "relay-room-websocket-url", "oauth-config", "oauth-delivery-origin", "relay-peer", "addRelay", "add-relay", "active-relay-fanout",
|
|
391
|
+
"relay-heartbeat-ms", "relay-heartbeat-timeout-ms", "relay-failover-ms", "max-events", "max-events-per-room",
|
|
392
|
+
"max-bytes-per-room", "max-room-data-total", "max-relay-data-total", "max-file-bytes",
|
|
393
|
+
"max-file-bytes-per-room", "max-file-bytes-total", "file-room-policy", "allow-file-room",
|
|
394
|
+
"deny-file-room", "quota-warning-ratio", "transport-mode",
|
|
395
|
+
"edge-socket", "socket", "edge-worker-id", "edge-worker-secret-file", "worker-secret-file", "package", "package-tag", "package-version", "worker-image", "worker-image-mode", "worker-image-prefix",
|
|
396
|
+
"worker-image-build-context", "worker-cli-package", "worker-controller", "compose-file",
|
|
397
|
+
"compose-project-directory", "compose-project-name", "release-storage-class", "storage-migration-class",
|
|
398
|
+
"storage-backup-dir", "rollout-state", "rollout-lock", "rollout-ready-timeout-ms",
|
|
399
|
+
"rollout-canary-timeout-ms", "rollout-flush-timeout-ms", "rollout-activation-timeout-ms",
|
|
400
|
+
"post-activation-bake-ms", "edge-queue", "edge-queue-path", "edge-max-queued-frames-per-connection",
|
|
401
|
+
"edge-max-queued-bytes-per-connection", "edge-max-queued-bytes-total", "edge-queue-backpressure-ratio",
|
|
402
|
+
"edge-max-migration-duration-ms", "max-events-per-pubkey-window", "pubkey-quota-window-seconds",
|
|
403
|
+
"max-event-bytes", "max-future-seconds", "sfu-peer-id", "sfu-max-participants", "ice"
|
|
404
|
+
]);
|
|
405
|
+
if (valueOptions.has(key)) requireCliValue(argv, index, token);
|
|
406
|
+
switch (key) {
|
|
407
|
+
case "host":
|
|
408
|
+
case "ipc-host":
|
|
409
|
+
return { index: index + 1, updates: { ipcHost: value } };
|
|
410
|
+
case "port":
|
|
411
|
+
case "ipc-port":
|
|
412
|
+
return { index: index + 1, updates: { ipcPort: Number(value) } };
|
|
413
|
+
case "data-dir":
|
|
414
|
+
return { index: index + 1, updates: { dataDir: value } };
|
|
415
|
+
case "storage":
|
|
416
|
+
return { index: index + 1, updates: { storagePath: value } };
|
|
417
|
+
case "profile":
|
|
418
|
+
case "matterhorn-profile":
|
|
419
|
+
return { index: index + 1, updates: { matterhornProfile: normalizeMatterhornProfile(value) } };
|
|
420
|
+
case "relay-peer-id":
|
|
421
|
+
return { index: index + 1, updates: { relayPeerId: value } };
|
|
422
|
+
case "name":
|
|
423
|
+
case "relay-name":
|
|
424
|
+
return { index: index + 1, updates: { relayName: value } };
|
|
425
|
+
case "relay-domain":
|
|
426
|
+
return { index: index + 1, updates: { relayDomain: value } };
|
|
427
|
+
case "relay-launcher-url":
|
|
428
|
+
return { index: index + 1, updates: { relayLauncherUrl: value } };
|
|
429
|
+
case "relay-vault-url":
|
|
430
|
+
return { index: index + 1, updates: { relayVaultUrl: value } };
|
|
431
|
+
case "relay-app-host-url":
|
|
432
|
+
return { index: index + 1, updates: { relayAppHostUrl: value } };
|
|
433
|
+
case "relay-room-websocket-url":
|
|
434
|
+
return { index: index + 1, updates: { relayRoomWebSocketUrl: value } };
|
|
435
|
+
case "relay-peer":
|
|
436
|
+
return { index: index + 1, updates: { relayPeers: [...args.relayPeers, value] } };
|
|
437
|
+
case "addRelay": case "add-relay":
|
|
438
|
+
return { index: index + 1, updates: { relayRefs: [...args.relayRefs, value] } };
|
|
439
|
+
case "nondiscoverable":
|
|
440
|
+
return { index, updates: { nondiscoverable: true } };
|
|
441
|
+
case "active-relay-fanout":
|
|
442
|
+
return { index: index + 1, updates: { activeRelayFanout: Number(value) } };
|
|
443
|
+
case "relay-heartbeat-ms":
|
|
444
|
+
return { index: index + 1, updates: { relayHeartbeatMs: Number(value) } };
|
|
445
|
+
case "relay-heartbeat-timeout-ms":
|
|
446
|
+
return { index: index + 1, updates: { relayHeartbeatTimeoutMs: Number(value) } };
|
|
447
|
+
case "relay-failover-ms":
|
|
448
|
+
return { index: index + 1, updates: { relayFailoverMs: Number(value) } };
|
|
449
|
+
case "max-events":
|
|
450
|
+
return { index: index + 1, updates: { maxEvents: Number(value) } };
|
|
451
|
+
case "max-events-per-room":
|
|
452
|
+
return { index: index + 1, updates: { maxEventsPerRoom: Number(value) } };
|
|
453
|
+
case "max-bytes-per-room":
|
|
454
|
+
case "max-room-data-total":
|
|
455
|
+
return { index: index + 1, updates: { maxBytesPerRoom: Number(value) } };
|
|
456
|
+
case "max-relay-data-total":
|
|
457
|
+
return { index: index + 1, updates: { maxBytesTotal: Number(value) } };
|
|
458
|
+
case "max-file-bytes":
|
|
459
|
+
return { index: index + 1, updates: { maxFileBytes: Number(value) } };
|
|
460
|
+
case "max-file-bytes-per-room":
|
|
461
|
+
return { index: index + 1, updates: { maxFileBytesPerRoom: Number(value) } };
|
|
462
|
+
case "max-file-bytes-total":
|
|
463
|
+
return { index: index + 1, updates: { maxFileBytesTotal: Number(value) } };
|
|
464
|
+
case "file-room-policy":
|
|
465
|
+
return { index: index + 1, updates: { fileRoomPolicy: String(value).trim().toLowerCase() } };
|
|
466
|
+
case "allow-file-room":
|
|
467
|
+
return { index: index + 1, updates: { fileAllowedRooms: [...args.fileAllowedRooms, value] } };
|
|
468
|
+
case "deny-file-room":
|
|
469
|
+
return { index: index + 1, updates: { fileDeniedRooms: [...args.fileDeniedRooms, value] } };
|
|
470
|
+
case "quota-warning-ratio":
|
|
471
|
+
return { index: index + 1, updates: { quotaWarningRatio: Number(value) } };
|
|
472
|
+
case "auto-trust-relays":
|
|
473
|
+
return { index, updates: { relayTrustMode: "accept" } };
|
|
474
|
+
case "manual-trust-relays":
|
|
475
|
+
case "no-auto-trust-relays":
|
|
476
|
+
return { index, updates: { relayTrustMode: "pending" } };
|
|
477
|
+
case "auto-reject-relays":
|
|
478
|
+
return { index, updates: { relayTrustMode: "reject" } };
|
|
479
|
+
case "transport-mode":
|
|
480
|
+
return { index: index + 1, updates: { transportMode: value } };
|
|
481
|
+
case "oauth":
|
|
482
|
+
return { index, updates: { oauthEnabled: true } };
|
|
483
|
+
case "no-oauth":
|
|
484
|
+
return { index, updates: { oauthEnabled: false } };
|
|
485
|
+
case "oauth-delivery-origin":
|
|
486
|
+
return { index: index + 1, updates: { oauthDeliveryOrigin: value } };
|
|
487
|
+
case "oauth-config":
|
|
488
|
+
return { index: index + 1, updates: { oauthConfigPath: value } };
|
|
489
|
+
case "edge-socket":
|
|
490
|
+
case "socket":
|
|
491
|
+
return { index: index + 1, updates: { edgeSocket: value } };
|
|
492
|
+
case "edge-worker-id":
|
|
493
|
+
return { index: index + 1, updates: { edgeWorkerId: value } };
|
|
494
|
+
case "edge-worker-secret-file":
|
|
495
|
+
case "worker-secret-file":
|
|
496
|
+
return { index: index + 1, updates: { edgeWorkerSecretFile: value } };
|
|
497
|
+
case "allow-insecure-worker-auth":
|
|
498
|
+
return { index, updates: { allowInsecureWorkerAuth: true } };
|
|
499
|
+
case "package":
|
|
500
|
+
return { index: index + 1, updates: { relayPackageName: value } };
|
|
501
|
+
case "package-tag":
|
|
502
|
+
return { index: index + 1, updates: { relayPackageTag: value } };
|
|
503
|
+
case "package-version":
|
|
504
|
+
return { index: index + 1, updates: { relayPackageVersion: value } };
|
|
505
|
+
case "worker-image":
|
|
506
|
+
return { index: index + 1, updates: { relayWorkerImage: value } };
|
|
507
|
+
case "worker-image-mode":
|
|
508
|
+
return { index: index + 1, updates: { relayWorkerImageMode: value } };
|
|
509
|
+
case "worker-image-prefix":
|
|
510
|
+
return { index: index + 1, updates: { relayWorkerImagePrefix: value } };
|
|
511
|
+
case "worker-image-build-context":
|
|
512
|
+
return { index: index + 1, updates: { relayWorkerImageBuildContext: value } };
|
|
513
|
+
case "worker-cli-package":
|
|
514
|
+
return { index: index + 1, updates: { relayWorkerCliPackageName: value } };
|
|
515
|
+
case "worker-controller":
|
|
516
|
+
return { index: index + 1, updates: { relayWorkerController: value } };
|
|
517
|
+
case "compose-file":
|
|
518
|
+
return { index: index + 1, updates: { relayWorkerComposeFile: value } };
|
|
519
|
+
case "compose-project-directory":
|
|
520
|
+
return { index: index + 1, updates: { relayWorkerComposeProjectDirectory: value } };
|
|
521
|
+
case "compose-project-name":
|
|
522
|
+
return { index: index + 1, updates: { relayWorkerComposeProjectName: value } };
|
|
523
|
+
case "release-storage-class":
|
|
524
|
+
case "storage-migration-class":
|
|
525
|
+
return { index: index + 1, updates: { relayReleaseStorageClass: value } };
|
|
526
|
+
case "allow-storage-breaking-rollout":
|
|
527
|
+
return { index, updates: { relayAllowStorageBreakingRollout: true } };
|
|
528
|
+
case "storage-backup-dir":
|
|
529
|
+
return { index: index + 1, updates: { relayStorageBackupDir: value } };
|
|
530
|
+
case "rollout-state":
|
|
531
|
+
return { index: index + 1, updates: { edgeRolloutStatePath: value } };
|
|
532
|
+
case "rollout-lock":
|
|
533
|
+
return { index: index + 1, updates: { edgeRolloutLockPath: value } };
|
|
534
|
+
case "rollout-ready-timeout-ms":
|
|
535
|
+
return { index: index + 1, updates: { edgeRolloutReadyTimeoutMs: Number(value) } };
|
|
536
|
+
case "rollout-canary-timeout-ms":
|
|
537
|
+
return { index: index + 1, updates: { edgeRolloutCanaryTimeoutMs: Number(value) } };
|
|
538
|
+
case "rollout-flush-timeout-ms":
|
|
539
|
+
return { index: index + 1, updates: { edgeRolloutFlushTimeoutMs: Number(value) } };
|
|
540
|
+
case "rollout-activation-timeout-ms":
|
|
541
|
+
return { index: index + 1, updates: { edgeRolloutActivationTimeoutMs: Number(value) } };
|
|
542
|
+
case "post-activation-bake-ms":
|
|
543
|
+
return { index: index + 1, updates: { edgePostActivationBakeMs: Number(value) } };
|
|
544
|
+
case "edge-queue":
|
|
545
|
+
case "edge-queue-path":
|
|
546
|
+
return { index: index + 1, updates: { edgeQueuePath: value } };
|
|
547
|
+
case "edge-max-queued-frames-per-connection":
|
|
548
|
+
return { index: index + 1, updates: { edgeMaxQueuedFramesPerConnection: Number(value) } };
|
|
549
|
+
case "edge-max-queued-bytes-per-connection":
|
|
550
|
+
return { index: index + 1, updates: { edgeMaxQueuedBytesPerConnection: Number(value) } };
|
|
551
|
+
case "edge-max-queued-bytes-total":
|
|
552
|
+
return { index: index + 1, updates: { edgeMaxQueuedBytesTotal: Number(value) } };
|
|
553
|
+
case "edge-queue-backpressure-ratio":
|
|
554
|
+
return { index: index + 1, updates: { edgeQueueBackpressureRatio: Number(value) } };
|
|
555
|
+
case "edge-max-migration-duration-ms":
|
|
556
|
+
return { index: index + 1, updates: { edgeMaxMigrationDurationMs: Number(value) } };
|
|
557
|
+
case "max-events-per-pubkey-window":
|
|
558
|
+
return { index: index + 1, updates: { maxEventsPerPubkeyWindow: Number(value) } };
|
|
559
|
+
case "pubkey-quota-window-seconds":
|
|
560
|
+
return { index: index + 1, updates: { pubkeyQuotaWindowSeconds: Number(value) } };
|
|
561
|
+
case "max-event-bytes":
|
|
562
|
+
return { index: index + 1, updates: { maxEventBytes: Number(value) } };
|
|
563
|
+
case "max-future-seconds":
|
|
564
|
+
return { index: index + 1, updates: { maxFutureSeconds: Number(value) } };
|
|
565
|
+
case "sfu":
|
|
566
|
+
return { index, updates: { sfuEnabled: true } };
|
|
567
|
+
case "no-sfu":
|
|
568
|
+
return { index, updates: { sfuEnabled: false } };
|
|
569
|
+
case "sfu-peer-id":
|
|
570
|
+
return { index: index + 1, updates: { sfuPeerId: value } };
|
|
571
|
+
case "sfu-max-participants":
|
|
572
|
+
return { index: index + 1, updates: { sfuMaxParticipants: Number(value) } };
|
|
573
|
+
case "ice":
|
|
574
|
+
return { index: index + 1, updates: { iceServers: value, iceServerConfigs: parseIceServers(value) } };
|
|
575
|
+
case "local-peerjs":
|
|
576
|
+
case "peerjs-local":
|
|
577
|
+
return { index, updates: { localPeerjs: true } };
|
|
578
|
+
case "no-local-peerjs":
|
|
579
|
+
return { index, updates: { localPeerjs: false } };
|
|
580
|
+
default:
|
|
581
|
+
warn(`Unknown relay option: ${token}`);
|
|
582
|
+
return { index, updates: {} };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function parseRelayArgs(argv, options = {}) {
|
|
587
|
+
const env = options.env || process.env;
|
|
588
|
+
const warn = options.warn || console.warn;
|
|
589
|
+
const defaultStoragePath = options.defaultStoragePath || defaultRelayStoragePath(options.defaultDataDir || defaultRuntimeDataDir());
|
|
590
|
+
const storageExplicit = Boolean(env.MATTERHORN_RELAY_STORAGE) || argv.includes("--storage");
|
|
591
|
+
const args = parseRelayArgsFromEnv(env, {
|
|
592
|
+
...options,
|
|
593
|
+
defaultStoragePath,
|
|
594
|
+
defaultIpcPort: options.defaultIpcPort || 42777
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
598
|
+
const token = argv[index];
|
|
599
|
+
if (!token.startsWith("--")) continue;
|
|
600
|
+
const result = applyRelayCliArg(args, token, index, argv, warn);
|
|
601
|
+
index = result.index;
|
|
602
|
+
Object.assign(args, result.updates);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (args.dataDir) args.dataDir = path.resolve(args.dataDir);
|
|
606
|
+
args.storagePath = storageExplicit
|
|
607
|
+
? resolveStoragePath(args.storagePath, defaultStoragePath)
|
|
608
|
+
: args.dataDir
|
|
609
|
+
? defaultRelayStoragePath(args.dataDir)
|
|
610
|
+
: resolveStoragePath(args.storagePath, defaultStoragePath);
|
|
611
|
+
if (args.edgeQueuePath) args.edgeQueuePath = resolveStoragePath(args.edgeQueuePath);
|
|
612
|
+
if (args.edgeRolloutStatePath) args.edgeRolloutStatePath = resolveStoragePath(args.edgeRolloutStatePath);
|
|
613
|
+
if (args.edgeRolloutLockPath) args.edgeRolloutLockPath = resolveStoragePath(args.edgeRolloutLockPath);
|
|
614
|
+
if (args.relayWorkerImageBuildContext) args.relayWorkerImageBuildContext = path.resolve(args.relayWorkerImageBuildContext);
|
|
615
|
+
if (args.relayWorkerComposeFile) args.relayWorkerComposeFile = path.resolve(args.relayWorkerComposeFile);
|
|
616
|
+
if (args.relayWorkerComposeProjectDirectory) args.relayWorkerComposeProjectDirectory = path.resolve(args.relayWorkerComposeProjectDirectory);
|
|
617
|
+
if (args.relayStorageBackupDir) args.relayStorageBackupDir = path.resolve(args.relayStorageBackupDir);
|
|
618
|
+
args.transportMode = resolveRelayTransportMode(args, { allowEdgeMode: options.allowEdgeMode === true });
|
|
619
|
+
return validateRelayNumericOptions(args);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function parseRelayEdgeArgs(argv, options = {}) {
|
|
623
|
+
const env = options.env || process.env;
|
|
624
|
+
const warn = options.warn || console.warn;
|
|
625
|
+
const defaultStoragePath = options.defaultStoragePath || defaultRelayStoragePath(options.defaultDataDir || defaultRuntimeDataDir());
|
|
626
|
+
const args = parseRelayArgsFromEnv(env, {
|
|
627
|
+
...options,
|
|
628
|
+
defaultStoragePath,
|
|
629
|
+
defaultIpcPort: options.defaultIpcPort || 42777
|
|
630
|
+
});
|
|
631
|
+
args.transportMode = RELAY_TRANSPORT_MODE_EDGE;
|
|
632
|
+
|
|
633
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
634
|
+
const token = argv[index];
|
|
635
|
+
if (!token.startsWith("--")) continue;
|
|
636
|
+
const result = applyRelayCliArg(args, token, index, argv, warn);
|
|
637
|
+
index = result.index;
|
|
638
|
+
Object.assign(args, result.updates);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
args.transportMode = RELAY_TRANSPORT_MODE_EDGE;
|
|
642
|
+
args.storagePath = resolveStoragePath(args.storagePath, defaultStoragePath);
|
|
643
|
+
if (args.edgeQueuePath) args.edgeQueuePath = resolveStoragePath(args.edgeQueuePath);
|
|
644
|
+
if (args.edgeRolloutStatePath) args.edgeRolloutStatePath = resolveStoragePath(args.edgeRolloutStatePath);
|
|
645
|
+
if (args.edgeRolloutLockPath) args.edgeRolloutLockPath = resolveStoragePath(args.edgeRolloutLockPath);
|
|
646
|
+
if (args.relayWorkerImageBuildContext) args.relayWorkerImageBuildContext = path.resolve(args.relayWorkerImageBuildContext);
|
|
647
|
+
if (args.relayWorkerComposeFile) args.relayWorkerComposeFile = path.resolve(args.relayWorkerComposeFile);
|
|
648
|
+
if (args.relayWorkerComposeProjectDirectory) args.relayWorkerComposeProjectDirectory = path.resolve(args.relayWorkerComposeProjectDirectory);
|
|
649
|
+
if (args.relayStorageBackupDir) args.relayStorageBackupDir = path.resolve(args.relayStorageBackupDir);
|
|
650
|
+
if (args.dataDir) args.dataDir = path.resolve(args.dataDir);
|
|
651
|
+
args.transportMode = resolveRelayTransportMode(args, { allowEdgeMode: true });
|
|
652
|
+
return validateRelayNumericOptions(args);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
module.exports = {
|
|
656
|
+
DEFAULT_RELAY_ACTIVE_FANOUT,
|
|
657
|
+
DEFAULT_RELAY_FAILOVER_MS,
|
|
658
|
+
DEFAULT_RELAY_EDGE_MAX_QUEUED_BYTES_PER_CONNECTION,
|
|
659
|
+
DEFAULT_RELAY_EDGE_MAX_QUEUED_BYTES_TOTAL,
|
|
660
|
+
DEFAULT_RELAY_EDGE_MAX_QUEUED_FRAMES_PER_CONNECTION,
|
|
661
|
+
DEFAULT_RELAY_EDGE_MAX_MIGRATION_DURATION_MS,
|
|
662
|
+
DEFAULT_RELAY_EDGE_QUEUE_BACKPRESSURE_RATIO,
|
|
663
|
+
DEFAULT_RELAY_HEARTBEAT_MS,
|
|
664
|
+
DEFAULT_RELAY_HEARTBEAT_TIMEOUT_MS,
|
|
665
|
+
DEFAULT_RELAY_MAX_CALL_SIGNAL_BYTES,
|
|
666
|
+
DEFAULT_RELAY_MAX_BYTES_PER_ROOM,
|
|
667
|
+
DEFAULT_RELAY_MAX_BYTES_TOTAL,
|
|
668
|
+
DEFAULT_RELAY_MAX_FILE_BYTES,
|
|
669
|
+
DEFAULT_RELAY_MAX_FILE_BYTES_PER_ROOM,
|
|
670
|
+
DEFAULT_RELAY_MAX_FILE_BYTES_TOTAL,
|
|
671
|
+
DEFAULT_RELAY_FILE_ROOM_POLICY,
|
|
672
|
+
DEFAULT_RELAY_MAX_EVENT_BYTES,
|
|
673
|
+
DEFAULT_RELAY_MAX_NIP44_EVENT_BYTES,
|
|
674
|
+
DEFAULT_RELAY_MAX_EVENTS_PER_PUBKEY_WINDOW,
|
|
675
|
+
DEFAULT_RELAY_MAX_EVENTS_PER_ROOM,
|
|
676
|
+
DEFAULT_RELAY_MAX_EVENTS,
|
|
677
|
+
DEFAULT_RELAY_MAX_FUTURE_SECONDS,
|
|
678
|
+
DEFAULT_RELAY_MAX_IPC_MESSAGE_BYTES,
|
|
679
|
+
DEFAULT_RELAY_MAX_IPC_MESSAGES_PER_WINDOW,
|
|
680
|
+
DEFAULT_RELAY_MAX_MESH_MESSAGE_BYTES,
|
|
681
|
+
DEFAULT_RELAY_MAX_MESH_MESSAGES_PER_WINDOW,
|
|
682
|
+
DEFAULT_RELAY_MAX_NOSTR_MESSAGE_BYTES,
|
|
683
|
+
DEFAULT_RELAY_MAX_ROOM_MESSAGE_BYTES,
|
|
684
|
+
DEFAULT_RELAY_MAX_ROOM_MESSAGES_PER_WINDOW,
|
|
685
|
+
DEFAULT_RELAY_MAX_UNAUTHENTICATED_MESSAGES_PER_WINDOW,
|
|
686
|
+
DEFAULT_RELAY_PUBKEY_QUOTA_WINDOW_SECONDS,
|
|
687
|
+
DEFAULT_RELAY_RATE_WINDOW_MS,
|
|
688
|
+
DEFAULT_RELAY_SFU_MAX_PARTICIPANTS,
|
|
689
|
+
MATTERHORN_PROFILE_ALPHA,
|
|
690
|
+
MATTERHORN_PROFILE_DEVELOPMENT,
|
|
691
|
+
RELAY_TRANSPORT_MODE_EDGE,
|
|
692
|
+
RELAY_TRANSPORT_MODE_EDGE_WORKER,
|
|
693
|
+
RELAY_TRANSPORT_MODE_STANDALONE,
|
|
694
|
+
MATTERHORN_PROFILE_PRODUCTION,
|
|
695
|
+
PROTOCOL,
|
|
696
|
+
defaultRuntimeDataDir,
|
|
697
|
+
defaultRelayStoragePath,
|
|
698
|
+
configuredIceServers,
|
|
699
|
+
defaultMatterhornProfile,
|
|
700
|
+
isMatterhornProfile,
|
|
701
|
+
parseExternalIceServersJson,
|
|
702
|
+
parseTurnIceServers,
|
|
703
|
+
normalizeIceMode,
|
|
704
|
+
normalizeMatterhornProfile,
|
|
705
|
+
normalizeRelayTransportMode,
|
|
706
|
+
parseBool,
|
|
707
|
+
parseIceServers,
|
|
708
|
+
parseRelayArgs,
|
|
709
|
+
parseRelayEdgeArgs,
|
|
710
|
+
parseRelayPeers,
|
|
711
|
+
redactIceServers,
|
|
712
|
+
resolveRelayTransportMode,
|
|
713
|
+
resolveStoragePath
|
|
714
|
+
};
|