replicas-computer 0.3.15 → 0.3.16
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 +1 -1
- package/dist/index.js +163 -101
- package/dist/stream-viewer.js +4 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ The server records every decision that changes the picture (viewer connects and
|
|
|
44
44
|
| `REPLICAS_DESKTOP_STREAM_TRANSPORT` | `webrtc` | Transport for viewers whose URL does not choose one |
|
|
45
45
|
| `REPLICAS_DESKTOP_STREAM_PROFILE` | `balanced` | Encoder profile for viewers whose URL does not choose one |
|
|
46
46
|
| `REPLICAS_DESKTOP_STREAM_ADAPTIVE` | `1` | `0` pins the top quality rung by default |
|
|
47
|
-
| `REPLICAS_DESKTOP_STREAM_ICE_SERVERS` | `stun:stun.l.google.com:19302` | Comma-separated STUN/TURN servers; `turn:user:secret@host:port?transport=tcp` adds a relay. Percent-encode the username and password; the browser receives their decoded values. Re-read from the workspace runtime env on every viewer, so the monolith's `DESKTOP_STREAM_ICE_SERVERS` reaches running workspaces |
|
|
47
|
+
| `REPLICAS_DESKTOP_STREAM_ICE_SERVERS` | `stun:stun.l.google.com:19302` | Comma-separated STUN/TURN servers; `turn:user:secret@host:port?transport=tcp` adds a relay. Percent-encode the username and password; the browser receives their decoded values. Re-read from the workspace runtime env on every viewer, so the monolith's `DESKTOP_STREAM_ICE_SERVERS` reaches running workspaces. Short-lived credentials for the [TURN relay](../services/turn/README.md) are fetched from the monolith and appended; the server itself only uses UDP relays |
|
|
48
48
|
| `REPLICAS_DESKTOP_STREAM_ICE_PORTS` | ephemeral | `begin-end` UDP port range for the server's ICE candidates |
|
|
49
49
|
| `REPLICAS_DESKTOP_STREAM_WEBRTC` | `1` | `0` serves the WebSocket transport only |
|
|
50
50
|
| `REPLICAS_DESKTOP_STREAM_COLOR` | `bt709` | `legacy` disables the explicit BT.709 conversion and signalling |
|
package/dist/index.js
CHANGED
|
@@ -2937,7 +2937,7 @@ async function computerStatusCommand() {
|
|
|
2937
2937
|
}
|
|
2938
2938
|
|
|
2939
2939
|
// src/version.ts
|
|
2940
|
-
var COMPUTER_USE_VERSION = "0.3.
|
|
2940
|
+
var COMPUTER_USE_VERSION = "0.3.16";
|
|
2941
2941
|
var COMPUTER_USE_PROTOCOL_VERSION = 1;
|
|
2942
2942
|
|
|
2943
2943
|
// src/program.ts
|
|
@@ -3179,69 +3179,11 @@ async function runComputerClient(argv) {
|
|
|
3179
3179
|
// src/media-server.ts
|
|
3180
3180
|
import { spawn as spawn5 } from "child_process";
|
|
3181
3181
|
import { createHash as createHash3 } from "crypto";
|
|
3182
|
-
import { createReadStream, existsSync as existsSync7, readdirSync, readFileSync as
|
|
3182
|
+
import { createReadStream, existsSync as existsSync7, readdirSync, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
|
|
3183
3183
|
import { createServer as createServer2 } from "http";
|
|
3184
3184
|
import { availableParallelism as availableParallelism2, loadavg } from "os";
|
|
3185
3185
|
import { dirname as dirname7, extname, resolve as resolve3 } from "path";
|
|
3186
3186
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3187
|
-
|
|
3188
|
-
// ../shared/src/runtime-env.ts
|
|
3189
|
-
function parsePosixEnvFile(content) {
|
|
3190
|
-
const result = {};
|
|
3191
|
-
let pos = 0;
|
|
3192
|
-
while (pos < content.length) {
|
|
3193
|
-
let lineStart;
|
|
3194
|
-
if (pos === 0 || content[pos - 1] === "\n") {
|
|
3195
|
-
lineStart = pos;
|
|
3196
|
-
} else {
|
|
3197
|
-
const nl2 = content.indexOf("\n", pos);
|
|
3198
|
-
if (nl2 === -1) break;
|
|
3199
|
-
lineStart = nl2 + 1;
|
|
3200
|
-
}
|
|
3201
|
-
if (!content.startsWith("export ", lineStart)) {
|
|
3202
|
-
const nl2 = content.indexOf("\n", lineStart);
|
|
3203
|
-
if (nl2 === -1) break;
|
|
3204
|
-
pos = nl2 + 1;
|
|
3205
|
-
continue;
|
|
3206
|
-
}
|
|
3207
|
-
const nameStart = lineStart + "export ".length;
|
|
3208
|
-
const eqIdx = content.indexOf("=", nameStart);
|
|
3209
|
-
if (eqIdx === -1) break;
|
|
3210
|
-
const name = content.slice(nameStart, eqIdx).trim();
|
|
3211
|
-
let cursor = eqIdx + 1;
|
|
3212
|
-
if (content[cursor] !== "'") {
|
|
3213
|
-
const nl2 = content.indexOf("\n", cursor);
|
|
3214
|
-
pos = nl2 === -1 ? content.length : nl2 + 1;
|
|
3215
|
-
continue;
|
|
3216
|
-
}
|
|
3217
|
-
cursor += 1;
|
|
3218
|
-
let value = "";
|
|
3219
|
-
let closed = false;
|
|
3220
|
-
while (cursor < content.length) {
|
|
3221
|
-
const ch = content[cursor];
|
|
3222
|
-
if (ch === "'") {
|
|
3223
|
-
if (content.startsWith("\\''", cursor + 1)) {
|
|
3224
|
-
value += "'";
|
|
3225
|
-
cursor += 4;
|
|
3226
|
-
continue;
|
|
3227
|
-
}
|
|
3228
|
-
cursor += 1;
|
|
3229
|
-
closed = true;
|
|
3230
|
-
break;
|
|
3231
|
-
}
|
|
3232
|
-
value += ch;
|
|
3233
|
-
cursor += 1;
|
|
3234
|
-
}
|
|
3235
|
-
if (closed && name) {
|
|
3236
|
-
result[name] = value;
|
|
3237
|
-
}
|
|
3238
|
-
const nl = content.indexOf("\n", cursor);
|
|
3239
|
-
pos = nl === -1 ? content.length : nl + 1;
|
|
3240
|
-
}
|
|
3241
|
-
return result;
|
|
3242
|
-
}
|
|
3243
|
-
|
|
3244
|
-
// src/media-server.ts
|
|
3245
3187
|
import WebSocket2, { WebSocketServer } from "ws";
|
|
3246
3188
|
|
|
3247
3189
|
// src/encoder.ts
|
|
@@ -3573,13 +3515,17 @@ function superviseEncoder(options) {
|
|
|
3573
3515
|
};
|
|
3574
3516
|
}
|
|
3575
3517
|
|
|
3576
|
-
// src/
|
|
3577
|
-
import {
|
|
3578
|
-
import { availableParallelism } from "os";
|
|
3579
|
-
import { dirname as dirname6, resolve as resolve2 } from "path";
|
|
3580
|
-
import { fileURLToPath } from "url";
|
|
3518
|
+
// src/ice-servers.ts
|
|
3519
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
3581
3520
|
|
|
3582
3521
|
// ../shared/src/desktop-stream.ts
|
|
3522
|
+
function isStreamIceServer(value) {
|
|
3523
|
+
return isRecord(value) && typeof value.urls === "string" && (value.username === void 0 || typeof value.username === "string") && (value.credential === void 0 || typeof value.credential === "string");
|
|
3524
|
+
}
|
|
3525
|
+
var DESKTOP_ICE_SERVERS_PATH = "/v1/engine/desktop/ice-servers";
|
|
3526
|
+
function isDesktopIceServers(value) {
|
|
3527
|
+
return isRecord(value) && typeof value.expiresAt === "number" && Array.isArray(value.iceServers) && value.iceServers.every(isStreamIceServer);
|
|
3528
|
+
}
|
|
3583
3529
|
var DEFAULT_STREAM_REQUEST = {
|
|
3584
3530
|
transport: "webrtc",
|
|
3585
3531
|
profile: "balanced",
|
|
@@ -3636,6 +3582,144 @@ function isDesktopWebRtcFailure(value) {
|
|
|
3636
3582
|
].every((key) => typeof value[key] === "string" && value[key].length <= 200) && ["elapsedMs", "candidateErrors", "framesDecoded", "packetsReceived", "packetsLost", "bytesReceived", "lastFrameAgeMs", "encoderFps"].every((key) => typeof value[key] === "number" && Number.isFinite(value[key]));
|
|
3637
3583
|
}
|
|
3638
3584
|
|
|
3585
|
+
// ../shared/src/runtime-env.ts
|
|
3586
|
+
var REPLICAS_RUNTIME_ENV_ALIASES = {
|
|
3587
|
+
monolithUrl: ["REPLICAS_MONOLITH_URL", "MONOLITH_URL"],
|
|
3588
|
+
workspaceId: ["REPLICAS_WORKSPACE_ID", "WORKSPACE_ID"],
|
|
3589
|
+
linearSessionId: ["REPLICAS_LINEAR_SESSION_ID", "LINEAR_SESSION_ID"],
|
|
3590
|
+
linearAccessToken: ["REPLICAS_LINEAR_ACCESS_TOKEN", "LINEAR_ACCESS_TOKEN"],
|
|
3591
|
+
slackBotToken: ["REPLICAS_SLACK_BOT_TOKEN", "SLACK_BOT_TOKEN"],
|
|
3592
|
+
slackChannelId: ["REPLICAS_SLACK_CHANNEL_ID", "SLACK_CHANNEL_ID"],
|
|
3593
|
+
slackThreadTs: ["REPLICAS_SLACK_THREAD_TS", "SLACK_THREAD_TS"]
|
|
3594
|
+
};
|
|
3595
|
+
function readReplicasRuntimeEnv(readEnv, [namespacedKey, legacyKey]) {
|
|
3596
|
+
return readEnv(namespacedKey) ?? readEnv(legacyKey);
|
|
3597
|
+
}
|
|
3598
|
+
function parsePosixEnvFile(content) {
|
|
3599
|
+
const result = {};
|
|
3600
|
+
let pos = 0;
|
|
3601
|
+
while (pos < content.length) {
|
|
3602
|
+
let lineStart;
|
|
3603
|
+
if (pos === 0 || content[pos - 1] === "\n") {
|
|
3604
|
+
lineStart = pos;
|
|
3605
|
+
} else {
|
|
3606
|
+
const nl2 = content.indexOf("\n", pos);
|
|
3607
|
+
if (nl2 === -1) break;
|
|
3608
|
+
lineStart = nl2 + 1;
|
|
3609
|
+
}
|
|
3610
|
+
if (!content.startsWith("export ", lineStart)) {
|
|
3611
|
+
const nl2 = content.indexOf("\n", lineStart);
|
|
3612
|
+
if (nl2 === -1) break;
|
|
3613
|
+
pos = nl2 + 1;
|
|
3614
|
+
continue;
|
|
3615
|
+
}
|
|
3616
|
+
const nameStart = lineStart + "export ".length;
|
|
3617
|
+
const eqIdx = content.indexOf("=", nameStart);
|
|
3618
|
+
if (eqIdx === -1) break;
|
|
3619
|
+
const name = content.slice(nameStart, eqIdx).trim();
|
|
3620
|
+
let cursor = eqIdx + 1;
|
|
3621
|
+
if (content[cursor] !== "'") {
|
|
3622
|
+
const nl2 = content.indexOf("\n", cursor);
|
|
3623
|
+
pos = nl2 === -1 ? content.length : nl2 + 1;
|
|
3624
|
+
continue;
|
|
3625
|
+
}
|
|
3626
|
+
cursor += 1;
|
|
3627
|
+
let value = "";
|
|
3628
|
+
let closed = false;
|
|
3629
|
+
while (cursor < content.length) {
|
|
3630
|
+
const ch = content[cursor];
|
|
3631
|
+
if (ch === "'") {
|
|
3632
|
+
if (content.startsWith("\\''", cursor + 1)) {
|
|
3633
|
+
value += "'";
|
|
3634
|
+
cursor += 4;
|
|
3635
|
+
continue;
|
|
3636
|
+
}
|
|
3637
|
+
cursor += 1;
|
|
3638
|
+
closed = true;
|
|
3639
|
+
break;
|
|
3640
|
+
}
|
|
3641
|
+
value += ch;
|
|
3642
|
+
cursor += 1;
|
|
3643
|
+
}
|
|
3644
|
+
if (closed && name) {
|
|
3645
|
+
result[name] = value;
|
|
3646
|
+
}
|
|
3647
|
+
const nl = content.indexOf("\n", cursor);
|
|
3648
|
+
pos = nl === -1 ? content.length : nl + 1;
|
|
3649
|
+
}
|
|
3650
|
+
return result;
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3653
|
+
// src/ice-servers.ts
|
|
3654
|
+
var runtimeEnvFile = process.env.REPLICAS_RUNTIME_ENV_FILE || "/home/user/.replicas/runtime-env.sh";
|
|
3655
|
+
var refreshMs = 60 * 6e4;
|
|
3656
|
+
var retryMs = 6e4;
|
|
3657
|
+
var relays = [];
|
|
3658
|
+
var expiresAt = 0;
|
|
3659
|
+
var refreshAt = 0;
|
|
3660
|
+
var refreshing;
|
|
3661
|
+
async function currentIceServers() {
|
|
3662
|
+
let env = process.env;
|
|
3663
|
+
try {
|
|
3664
|
+
env = { ...env, ...parsePosixEnvFile(readFileSync6(runtimeEnvFile, "utf8")) };
|
|
3665
|
+
} catch {
|
|
3666
|
+
}
|
|
3667
|
+
const monolithUrl = readReplicasRuntimeEnv((name) => env[name], REPLICAS_RUNTIME_ENV_ALIASES.monolithUrl);
|
|
3668
|
+
const workspaceId = readReplicasRuntimeEnv((name) => env[name], REPLICAS_RUNTIME_ENV_ALIASES.workspaceId);
|
|
3669
|
+
const engineSecret = env.REPLICAS_ENGINE_SECRET;
|
|
3670
|
+
if (monolithUrl && workspaceId && engineSecret && Date.now() >= refreshAt) {
|
|
3671
|
+
const refreshed = refreshing ??= refreshRelays(monolithUrl, workspaceId, engineSecret).finally(() => {
|
|
3672
|
+
refreshing = void 0;
|
|
3673
|
+
});
|
|
3674
|
+
if (expiresAt <= Date.now()) await refreshed;
|
|
3675
|
+
}
|
|
3676
|
+
return toIceServers(env.REPLICAS_DESKTOP_STREAM_ICE_SERVERS, expiresAt > Date.now() ? relays : []);
|
|
3677
|
+
}
|
|
3678
|
+
async function refreshRelays(monolithUrl, workspaceId, engineSecret) {
|
|
3679
|
+
try {
|
|
3680
|
+
const response = await fetch(new URL(DESKTOP_ICE_SERVERS_PATH, monolithUrl), {
|
|
3681
|
+
headers: { authorization: `Bearer ${engineSecret}`, "x-workspace-id": workspaceId },
|
|
3682
|
+
redirect: "error",
|
|
3683
|
+
signal: AbortSignal.timeout(2e3)
|
|
3684
|
+
});
|
|
3685
|
+
if (response.status === 404) {
|
|
3686
|
+
relays = [];
|
|
3687
|
+
expiresAt = 0;
|
|
3688
|
+
} else {
|
|
3689
|
+
const body = response.ok ? await response.json() : void 0;
|
|
3690
|
+
if (!isDesktopIceServers(body)) throw new Error(`HTTP ${response.status}`);
|
|
3691
|
+
({ iceServers: relays, expiresAt } = body);
|
|
3692
|
+
}
|
|
3693
|
+
refreshAt = Date.now() + refreshMs;
|
|
3694
|
+
} catch (error) {
|
|
3695
|
+
process.stderr.write(`[desktop-stream] TURN credentials unavailable: ${error instanceof Error ? error.message : String(error)}
|
|
3696
|
+
`);
|
|
3697
|
+
refreshAt = Date.now() + retryMs;
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
function toIceServers(spec = "stun:stun.l.google.com:19302", relayServers) {
|
|
3701
|
+
const browser = spec.split(",").map((url) => url.trim()).filter(Boolean).map((url) => {
|
|
3702
|
+
const match = url.match(/^(turns?):([^:@]+):(.+)@([^@]+)$/);
|
|
3703
|
+
if (!match) return { urls: url };
|
|
3704
|
+
const [username, credential] = match.slice(2, 4).map((value) => {
|
|
3705
|
+
try {
|
|
3706
|
+
return decodeURIComponent(value);
|
|
3707
|
+
} catch {
|
|
3708
|
+
return value;
|
|
3709
|
+
}
|
|
3710
|
+
});
|
|
3711
|
+
return { urls: `${match[1]}:${match[4]}`, username, credential };
|
|
3712
|
+
}).concat(relayServers);
|
|
3713
|
+
const native = browser.filter(({ urls }) => !/^turns:|[?&]transport=tcp/i.test(urls)).map(({ urls, username, credential }) => username === void 0 || credential === void 0 ? urls : urls.replace(/^turn:/i, `turn:${encodeURIComponent(username)}:${encodeURIComponent(credential)}@`));
|
|
3714
|
+
return { native, browser };
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
// src/video-encoder.ts
|
|
3718
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3719
|
+
import { availableParallelism } from "os";
|
|
3720
|
+
import { dirname as dirname6, resolve as resolve2 } from "path";
|
|
3721
|
+
import { fileURLToPath } from "url";
|
|
3722
|
+
|
|
3639
3723
|
// src/input-validation.ts
|
|
3640
3724
|
var finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
3641
3725
|
var optionalFinite = (value) => value === void 0 || finite(value);
|
|
@@ -3951,22 +4035,6 @@ async function loadWebRtc() {
|
|
|
3951
4035
|
}
|
|
3952
4036
|
}
|
|
3953
4037
|
var cleanupWebRtc = () => ndc?.cleanup();
|
|
3954
|
-
function parseIceServers(spec) {
|
|
3955
|
-
const native = (spec ?? "stun:stun.l.google.com:19302").split(",").map((server) => server.trim()).filter(Boolean);
|
|
3956
|
-
const browser = native.map((url) => {
|
|
3957
|
-
const match = url.match(/^(turns?):([^:@]+):(.+)@([^@]+)$/);
|
|
3958
|
-
if (!match) return { urls: url };
|
|
3959
|
-
const [username, credential] = match.slice(2, 4).map((value) => {
|
|
3960
|
-
try {
|
|
3961
|
-
return decodeURIComponent(value);
|
|
3962
|
-
} catch {
|
|
3963
|
-
return value;
|
|
3964
|
-
}
|
|
3965
|
-
});
|
|
3966
|
-
return { urls: `${match[1]}:${match[4]}`, username, credential };
|
|
3967
|
-
});
|
|
3968
|
-
return { native, browser };
|
|
3969
|
-
}
|
|
3970
4038
|
var WebRtcViewer = class {
|
|
3971
4039
|
constructor(options, handlers) {
|
|
3972
4040
|
this.handlers = handlers;
|
|
@@ -4478,7 +4546,6 @@ var standbyRung = 5;
|
|
|
4478
4546
|
var port = Number.parseInt(process.env.REPLICAS_DESKTOP_STREAM_PORT || String(DESKTOP_STREAM_PORT), 10);
|
|
4479
4547
|
var display = process.env.REPLICAS_DESKTOP_DISPLAY?.replace(/^([^:])/, ":$1") || ":99";
|
|
4480
4548
|
var webrtcEnabled = process.env.REPLICAS_DESKTOP_STREAM_WEBRTC !== "0";
|
|
4481
|
-
var runtimeEnvFile = process.env.REPLICAS_RUNTIME_ENV_FILE || "/home/user/.replicas/runtime-env.sh";
|
|
4482
4549
|
var colorMode = process.env.REPLICAS_DESKTOP_STREAM_COLOR === "legacy" ? "legacy" : "bt709";
|
|
4483
4550
|
var defaultRequest = parseStreamRequest(new URLSearchParams({
|
|
4484
4551
|
transport: process.env.REPLICAS_DESKTOP_STREAM_TRANSPORT ?? "",
|
|
@@ -4534,14 +4601,6 @@ function mediaPacket(kind, timestamp, payload) {
|
|
|
4534
4601
|
header.writeBigUInt64BE(BigInt(Math.max(0, timestamp)), 4);
|
|
4535
4602
|
return Buffer.concat([header, payload]);
|
|
4536
4603
|
}
|
|
4537
|
-
function currentIceServers() {
|
|
4538
|
-
let spec = process.env.REPLICAS_DESKTOP_STREAM_ICE_SERVERS;
|
|
4539
|
-
try {
|
|
4540
|
-
spec = parsePosixEnvFile(readFileSync6(runtimeEnvFile, "utf8")).REPLICAS_DESKTOP_STREAM_ICE_SERVERS ?? spec;
|
|
4541
|
-
} catch {
|
|
4542
|
-
}
|
|
4543
|
-
return parseIceServers(spec);
|
|
4544
|
-
}
|
|
4545
4604
|
var appliedTheme;
|
|
4546
4605
|
var pendingTheme;
|
|
4547
4606
|
var applyingTheme = false;
|
|
@@ -4567,7 +4626,7 @@ function drainDesktopTheme() {
|
|
|
4567
4626
|
function processCpuMs(pid) {
|
|
4568
4627
|
if (!pid) return 0;
|
|
4569
4628
|
try {
|
|
4570
|
-
const stat =
|
|
4629
|
+
const stat = readFileSync7(`/proc/${pid}/stat`, "utf8");
|
|
4571
4630
|
const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
4572
4631
|
return (Number(fields[11]) + Number(fields[12])) * 10;
|
|
4573
4632
|
} catch {
|
|
@@ -5252,10 +5311,13 @@ async function runMediaServer() {
|
|
|
5252
5311
|
// A socket viewer whose WebRTC attempt failed tries again in the background;
|
|
5253
5312
|
// the socket keeps carrying its media until the new peer shows video.
|
|
5254
5313
|
case "retry-webrtc": {
|
|
5255
|
-
const
|
|
5256
|
-
if (!
|
|
5257
|
-
|
|
5258
|
-
|
|
5314
|
+
const retryable = () => webrtc && viewer.fallback && !viewer.rtc && viewer.request.transport === "webrtc" && viewer.socket.readyState === WebSocket2.OPEN;
|
|
5315
|
+
if (!retryable()) return;
|
|
5316
|
+
void currentIceServers().then((ice) => {
|
|
5317
|
+
if (!retryable()) return;
|
|
5318
|
+
record("retrying webrtc", "socket media continues meanwhile");
|
|
5319
|
+
openWebRtc(viewer, reply, ice.native);
|
|
5320
|
+
});
|
|
5259
5321
|
return;
|
|
5260
5322
|
}
|
|
5261
5323
|
case "webrtc-ready":
|
|
@@ -5359,10 +5421,11 @@ async function runMediaServer() {
|
|
|
5359
5421
|
if (extname(path) === ".html") {
|
|
5360
5422
|
ensureEncoders();
|
|
5361
5423
|
if (viewers.size === 0) stopEncodersSoon();
|
|
5424
|
+
if (webrtc) void currentIceServers();
|
|
5362
5425
|
response.setHeader("cache-control", "no-store");
|
|
5363
5426
|
const digest = createHash3("sha1");
|
|
5364
|
-
for (const name of readdirSync(webroot).filter((file) => /\.(js|css)$/.test(file)).sort()) digest.update(
|
|
5365
|
-
response.end(
|
|
5427
|
+
for (const name of readdirSync(webroot).filter((file) => /\.(js|css)$/.test(file)).sort()) digest.update(readFileSync7(resolve3(webroot, name)));
|
|
5428
|
+
response.end(readFileSync7(path, "utf8").replaceAll("__VERSION__", `${COMPUTER_USE_VERSION}-${digest.digest("hex").slice(0, 8)}`));
|
|
5366
5429
|
return;
|
|
5367
5430
|
}
|
|
5368
5431
|
response.setHeader("cache-control", "private, max-age=31536000, immutable");
|
|
@@ -5375,9 +5438,10 @@ async function runMediaServer() {
|
|
|
5375
5438
|
socket.destroy();
|
|
5376
5439
|
return;
|
|
5377
5440
|
}
|
|
5378
|
-
|
|
5441
|
+
const streamRequest = parseStreamRequest(url.searchParams, defaultRequest);
|
|
5442
|
+
void (webrtc && streamRequest.transport === "webrtc" ? currentIceServers() : Promise.resolve(void 0)).then((ice) => webSocketServer.handleUpgrade(request, socket, head, (client) => webSocketServer.emit("connection", client, streamRequest, url.searchParams.get("standby") === "1", ice)));
|
|
5379
5443
|
});
|
|
5380
|
-
webSocketServer.on("connection", (socket, request, standby) => {
|
|
5444
|
+
webSocketServer.on("connection", (socket, request, standby, ice) => {
|
|
5381
5445
|
const viewer = {
|
|
5382
5446
|
socket,
|
|
5383
5447
|
input: new InputBridge(display, width, height),
|
|
@@ -5411,8 +5475,6 @@ async function runMediaServer() {
|
|
|
5411
5475
|
const reply = (message) => {
|
|
5412
5476
|
if (socket.readyState === WebSocket2.OPEN) socket.send(JSON.stringify(message));
|
|
5413
5477
|
};
|
|
5414
|
-
const offerWebRtc = webrtc && request.transport === "webrtc";
|
|
5415
|
-
const ice = offerWebRtc ? currentIceServers() : void 0;
|
|
5416
5478
|
const fallbackProfile = profile() === "high444" ? "main" : profile();
|
|
5417
5479
|
const configMessage = { ...config, videoCodec: H264_PROFILES[fallbackProfile].codec, iceServers: ice?.browser, request };
|
|
5418
5480
|
reply(configMessage);
|
package/dist/stream-viewer.js
CHANGED
|
@@ -4,6 +4,9 @@ function isRecord(value) {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
// ../shared/src/desktop-stream.ts
|
|
7
|
+
function isStreamIceServer(value) {
|
|
8
|
+
return isRecord(value) && typeof value.urls === "string" && (value.username === void 0 || typeof value.username === "string") && (value.credential === void 0 || typeof value.credential === "string");
|
|
9
|
+
}
|
|
7
10
|
var DEFAULT_STREAM_REQUEST = {
|
|
8
11
|
transport: "webrtc",
|
|
9
12
|
profile: "balanced",
|
|
@@ -49,10 +52,6 @@ var DESKTOP_QUALITY_MESSAGE = "replicas-desktop-quality";
|
|
|
49
52
|
|
|
50
53
|
// src/input-validation.ts
|
|
51
54
|
var finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
52
|
-
var iceServer = (value) => {
|
|
53
|
-
if (!isRecord(value)) return false;
|
|
54
|
-
return typeof value.urls === "string" && (value.username === void 0 || typeof value.username === "string") && (value.credential === void 0 || typeof value.credential === "string");
|
|
55
|
-
};
|
|
56
55
|
function validServerMessage(value) {
|
|
57
56
|
if (!isRecord(value) || typeof value.type !== "string") return false;
|
|
58
57
|
switch (value.type) {
|
|
@@ -66,7 +65,7 @@ function validServerMessage(value) {
|
|
|
66
65
|
case "candidate":
|
|
67
66
|
return typeof value.candidate === "string" && value.candidate.length < 1024 && typeof value.mid === "string" && value.mid.length < 16;
|
|
68
67
|
case "config":
|
|
69
|
-
return finite(value.protocolVersion) && (value.serverVersion === void 0 || typeof value.serverVersion === "string") && (value.webrtcUnavailableReason === void 0 || typeof value.webrtcUnavailableReason === "string") && (value.inputRace === void 0 || typeof value.inputRace === "boolean") && finite(value.width) && finite(value.height) && finite(value.fps) && typeof value.videoCodec === "string" && typeof value.audioCodec === "string" && finite(value.audioSampleRate) && finite(value.audioChannels) && isRecord(value.request) && (value.iceServers === void 0 || Array.isArray(value.iceServers) && value.iceServers.every(
|
|
68
|
+
return finite(value.protocolVersion) && (value.serverVersion === void 0 || typeof value.serverVersion === "string") && (value.webrtcUnavailableReason === void 0 || typeof value.webrtcUnavailableReason === "string") && (value.inputRace === void 0 || typeof value.inputRace === "boolean") && finite(value.width) && finite(value.height) && finite(value.fps) && typeof value.videoCodec === "string" && typeof value.audioCodec === "string" && finite(value.audioSampleRate) && finite(value.audioChannels) && isRecord(value.request) && (value.iceServers === void 0 || Array.isArray(value.iceServers) && value.iceServers.every(isStreamIceServer));
|
|
70
69
|
case "telemetry":
|
|
71
70
|
return isRecord(value.encoder) && isRecord(value.cpu) && isRecord(value.viewer) && Array.isArray(value.events);
|
|
72
71
|
default:
|