ds-harness-remote 0.3.32 → 0.3.34
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 +3 -3
- package/dist/client-runtime.d.ts +2 -1
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +73 -31
- package/dist/client-runtime.js.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.github.js +88 -54
- package/dist/client.js +88 -54
- package/dist/client.js.map +1 -1
- package/dist/harness-api-bridge.d.ts.map +1 -1
- package/dist/harness-api-bridge.js +2 -5
- package/dist/harness-api-bridge.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1384 -113
- package/dist/index.js.map +4 -4
- package/dist/native-rtc-helper.d.ts +4 -0
- package/dist/native-rtc-helper.d.ts.map +1 -0
- package/dist/native-rtc-helper.js +539 -0
- package/dist/native-rtc-helper.js.map +1 -0
- package/dist/rpc-router.d.ts +1 -6
- package/dist/rpc-router.d.ts.map +1 -1
- package/dist/rpc-router.js +5 -20
- package/dist/rpc-router.js.map +1 -1
- package/dist/safe-error.d.ts +8 -0
- package/dist/safe-error.d.ts.map +1 -0
- package/dist/safe-error.js +20 -0
- package/dist/safe-error.js.map +1 -0
- package/dist/server-connection.d.ts.map +1 -1
- package/dist/server-connection.js +43 -2
- package/dist/server-connection.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +4 -2
- package/dist/service.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/werift-rtc.d.ts +45 -9
- package/dist/werift-rtc.d.ts.map +1 -1
- package/dist/werift-rtc.js +356 -35
- package/dist/werift-rtc.js.map +1 -1
- package/package.json +4 -1
- package/public.d.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -4574,7 +4574,7 @@ var RTC_DATA_CHANNEL_OPTIONS = { ordered: true };
|
|
|
4574
4574
|
function browserRtcFactory() {
|
|
4575
4575
|
return {
|
|
4576
4576
|
create(configuration) {
|
|
4577
|
-
const raw = new RTCPeerConnection(configuration);
|
|
4577
|
+
const raw = new RTCPeerConnection({ iceServers: configuration.iceServers });
|
|
4578
4578
|
return {
|
|
4579
4579
|
get connectionState() {
|
|
4580
4580
|
return raw.connectionState;
|
|
@@ -4646,6 +4646,97 @@ function browserRtcFactory() {
|
|
|
4646
4646
|
}
|
|
4647
4647
|
};
|
|
4648
4648
|
}
|
|
4649
|
+
function summarizeIceCandidate(candidate) {
|
|
4650
|
+
const value = candidate?.candidate?.trim();
|
|
4651
|
+
if (value === void 0 || value.length === 0)
|
|
4652
|
+
return emptyCandidateSummary();
|
|
4653
|
+
const parts = value.replace(/^candidate:/, "").split(/\s+/);
|
|
4654
|
+
const typeIndex = parts.indexOf("typ");
|
|
4655
|
+
const relatedAddressIndex = parts.indexOf("raddr");
|
|
4656
|
+
return {
|
|
4657
|
+
candidateType: parseCandidateType(parts[typeIndex + 1]),
|
|
4658
|
+
protocol: normalizeProtocol(parts[2]),
|
|
4659
|
+
...summarizeAddress(parts[4]),
|
|
4660
|
+
...relatedAddressIndex < 0 ? {} : relatedAddressSummary(parts[relatedAddressIndex + 1])
|
|
4661
|
+
};
|
|
4662
|
+
}
|
|
4663
|
+
function summarizeAddress(address) {
|
|
4664
|
+
if (address === void 0 || address.length === 0)
|
|
4665
|
+
return { addressFamily: "unknown", addressScope: "unknown" };
|
|
4666
|
+
if (address.includes(":"))
|
|
4667
|
+
return { addressFamily: "ipv6", addressScope: summarizeIpv6Scope(address) };
|
|
4668
|
+
const parts = address.split(".").map(Number);
|
|
4669
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
4670
|
+
return { addressFamily: "unknown", addressScope: "unknown" };
|
|
4671
|
+
}
|
|
4672
|
+
return { addressFamily: "ipv4", addressScope: summarizeIpv4Scope(parts) };
|
|
4673
|
+
}
|
|
4674
|
+
function stunOnlyIceServers(iceServers) {
|
|
4675
|
+
const direct = [];
|
|
4676
|
+
for (const server of iceServers) {
|
|
4677
|
+
const sourceUrls = Array.isArray(server.urls) ? server.urls : [server.urls];
|
|
4678
|
+
const urls = sourceUrls.filter(isStunUrl);
|
|
4679
|
+
if (urls.length === 0)
|
|
4680
|
+
continue;
|
|
4681
|
+
direct.push({ urls: Array.isArray(server.urls) ? urls : urls[0] });
|
|
4682
|
+
}
|
|
4683
|
+
return direct;
|
|
4684
|
+
}
|
|
4685
|
+
function isStunUrl(url) {
|
|
4686
|
+
const value = url.trim().toLowerCase();
|
|
4687
|
+
return value.startsWith("stun:") || value.startsWith("stuns:");
|
|
4688
|
+
}
|
|
4689
|
+
function emptyCandidateSummary() {
|
|
4690
|
+
return { candidateType: "unknown", addressFamily: "unknown", addressScope: "unknown" };
|
|
4691
|
+
}
|
|
4692
|
+
function parseCandidateType(value) {
|
|
4693
|
+
if (value === "host" || value === "srflx" || value === "prflx" || value === "relay")
|
|
4694
|
+
return value;
|
|
4695
|
+
return "unknown";
|
|
4696
|
+
}
|
|
4697
|
+
function normalizeProtocol(value) {
|
|
4698
|
+
if (value === void 0 || value.length === 0)
|
|
4699
|
+
return void 0;
|
|
4700
|
+
return value.toLowerCase();
|
|
4701
|
+
}
|
|
4702
|
+
function relatedAddressSummary(address) {
|
|
4703
|
+
const summary = summarizeAddress(address);
|
|
4704
|
+
return {
|
|
4705
|
+
relatedAddressFamily: summary.addressFamily,
|
|
4706
|
+
relatedAddressScope: summary.addressScope
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4709
|
+
function summarizeIpv4Scope(parts) {
|
|
4710
|
+
if (parts[0] === 0 || parts[0] >= 240)
|
|
4711
|
+
return "reserved";
|
|
4712
|
+
if (parts[0] === 127)
|
|
4713
|
+
return "loopback";
|
|
4714
|
+
if (parts[0] === 169 && parts[1] === 254)
|
|
4715
|
+
return "link-local";
|
|
4716
|
+
if (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127)
|
|
4717
|
+
return "cgnat";
|
|
4718
|
+
if (parts[0] >= 224)
|
|
4719
|
+
return "reserved";
|
|
4720
|
+
if (parts[0] === 10)
|
|
4721
|
+
return "private";
|
|
4722
|
+
if (parts[0] === 192 && parts[1] === 168)
|
|
4723
|
+
return "private";
|
|
4724
|
+
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
|
|
4725
|
+
return "private";
|
|
4726
|
+
return "public";
|
|
4727
|
+
}
|
|
4728
|
+
function summarizeIpv6Scope(address) {
|
|
4729
|
+
const value = address.toLowerCase();
|
|
4730
|
+
if (value === "::1")
|
|
4731
|
+
return "loopback";
|
|
4732
|
+
if (value.startsWith("fe80:"))
|
|
4733
|
+
return "link-local";
|
|
4734
|
+
if (value.startsWith("fc") || value.startsWith("fd"))
|
|
4735
|
+
return "private";
|
|
4736
|
+
if (value.startsWith("ff"))
|
|
4737
|
+
return "reserved";
|
|
4738
|
+
return "public";
|
|
4739
|
+
}
|
|
4649
4740
|
function adaptDataChannel(raw) {
|
|
4650
4741
|
return {
|
|
4651
4742
|
get label() {
|
|
@@ -4823,6 +4914,8 @@ var RtcDataChannelTransport = class {
|
|
|
4823
4914
|
negotiateTimeoutMs;
|
|
4824
4915
|
channelLabel;
|
|
4825
4916
|
sendTimeoutMs;
|
|
4917
|
+
label;
|
|
4918
|
+
onDiagnostic;
|
|
4826
4919
|
channel;
|
|
4827
4920
|
remoteCandidates = [];
|
|
4828
4921
|
remoteDescriptionSet = false;
|
|
@@ -4843,6 +4936,12 @@ var RtcDataChannelTransport = class {
|
|
|
4843
4936
|
bytesReceived = 0;
|
|
4844
4937
|
selected;
|
|
4845
4938
|
selectedMode;
|
|
4939
|
+
selectedPathTelemetry;
|
|
4940
|
+
localCandidateTelemetry = emptyCandidateTelemetry();
|
|
4941
|
+
remoteCandidateTelemetry = emptyCandidateTelemetry();
|
|
4942
|
+
candidatePairTelemetry = emptyCandidatePairTelemetry();
|
|
4943
|
+
filteredLocalCandidateTelemetry = emptyCandidateTelemetry();
|
|
4944
|
+
filteredCandidatePairTelemetry = emptyCandidateTelemetry();
|
|
4846
4945
|
lastBufferedAmount = 0;
|
|
4847
4946
|
constructor(options) {
|
|
4848
4947
|
this.role = options.role;
|
|
@@ -4850,15 +4949,30 @@ var RtcDataChannelTransport = class {
|
|
|
4850
4949
|
this.negotiateTimeoutMs = options.negotiateTimeoutMs ?? DEFAULT_NEGOTIATE_TIMEOUT_MS;
|
|
4851
4950
|
this.channelLabel = options.channelLabel ?? RTC_DATA_CHANNEL_LABEL;
|
|
4852
4951
|
this.sendTimeoutMs = options.sendTimeoutMs;
|
|
4853
|
-
this.
|
|
4952
|
+
this.label = options.label;
|
|
4953
|
+
this.onDiagnostic = options.onDiagnostic;
|
|
4954
|
+
this.pc = options.factory.create({
|
|
4955
|
+
iceServers: options.iceServers,
|
|
4956
|
+
onDiagnostic: (event) => this.handlePeerDiagnostic(event)
|
|
4957
|
+
});
|
|
4854
4958
|
this.pc.ondatachannel = (event) => this.adoptChannel(event.channel);
|
|
4855
4959
|
this.pc.onicecandidate = (event) => {
|
|
4856
4960
|
if (event.candidate !== null && !this.closed) {
|
|
4961
|
+
const candidate = summarizeIceCandidate(event.candidate);
|
|
4962
|
+
recordCandidate(this.localCandidateTelemetry, candidate);
|
|
4963
|
+
this.emitDiagnostic({ type: "local-candidate", candidate, diagnostics: this.diagnostics() });
|
|
4857
4964
|
this.onSignal({ type: "ice", candidate: event.candidate });
|
|
4858
4965
|
}
|
|
4859
4966
|
};
|
|
4860
|
-
this.pc.onconnectionstatechange = () =>
|
|
4861
|
-
|
|
4967
|
+
this.pc.onconnectionstatechange = () => {
|
|
4968
|
+
this.emitStateChange();
|
|
4969
|
+
this.checkConnectionState();
|
|
4970
|
+
};
|
|
4971
|
+
this.pc.oniceconnectionstatechange = () => {
|
|
4972
|
+
this.emitStateChange();
|
|
4973
|
+
this.checkConnectionState();
|
|
4974
|
+
};
|
|
4975
|
+
this.pc.onicegatheringstatechange = () => this.emitStateChange();
|
|
4862
4976
|
}
|
|
4863
4977
|
/** Begin negotiation; resolves when the DataChannel is open. */
|
|
4864
4978
|
connect(signal) {
|
|
@@ -4871,7 +4985,7 @@ var RtcDataChannelTransport = class {
|
|
|
4871
4985
|
this.openResolve = resolve2;
|
|
4872
4986
|
this.openReject = reject;
|
|
4873
4987
|
this.negotiateTimer = setTimeout(() => {
|
|
4874
|
-
this.
|
|
4988
|
+
void this.failOpenAfterStats(new RtcConnectError("RTC_CONNECT_TIMEOUT", `WebRTC negotiation timed out after ${this.negotiateTimeoutMs}ms.`));
|
|
4875
4989
|
}, this.negotiateTimeoutMs);
|
|
4876
4990
|
if (this.role === "initiator") {
|
|
4877
4991
|
void this.startInitiator().catch((error) => this.failOpen(asError(error)));
|
|
@@ -4922,10 +5036,25 @@ var RtcDataChannelTransport = class {
|
|
|
4922
5036
|
selectedPathMode() {
|
|
4923
5037
|
return this.selectedMode;
|
|
4924
5038
|
}
|
|
5039
|
+
diagnostics() {
|
|
5040
|
+
return {
|
|
5041
|
+
role: this.role,
|
|
5042
|
+
...this.label === void 0 ? {} : { label: this.label },
|
|
5043
|
+
connectionState: this.pc.connectionState,
|
|
5044
|
+
iceConnectionState: this.pc.iceConnectionState,
|
|
5045
|
+
iceGatheringState: this.pc.iceGatheringState,
|
|
5046
|
+
localCandidates: cloneCandidateTelemetry(this.localCandidateTelemetry),
|
|
5047
|
+
remoteCandidates: cloneCandidateTelemetry(this.remoteCandidateTelemetry),
|
|
5048
|
+
candidatePairs: cloneCandidatePairTelemetry(this.candidatePairTelemetry),
|
|
5049
|
+
filteredLocalCandidates: cloneCandidateTelemetry(this.filteredLocalCandidateTelemetry),
|
|
5050
|
+
filteredCandidatePairs: cloneCandidateTelemetry(this.filteredCandidatePairTelemetry),
|
|
5051
|
+
...this.selectedPathTelemetry === void 0 ? {} : { selectedPath: { ...this.selectedPathTelemetry } }
|
|
5052
|
+
};
|
|
5053
|
+
}
|
|
4925
5054
|
async connectionDetails() {
|
|
4926
5055
|
let statsDetails = {};
|
|
4927
5056
|
try {
|
|
4928
|
-
statsDetails =
|
|
5057
|
+
statsDetails = await this.refreshStatsDiagnostics();
|
|
4929
5058
|
} catch {
|
|
4930
5059
|
}
|
|
4931
5060
|
return {
|
|
@@ -4933,7 +5062,8 @@ var RtcDataChannelTransport = class {
|
|
|
4933
5062
|
mode: statsDetails.mode ?? this.selectedMode,
|
|
4934
5063
|
connectionState: this.pc.connectionState,
|
|
4935
5064
|
iceConnectionState: this.pc.iceConnectionState,
|
|
4936
|
-
dataChannelState: this.channel?.readyState
|
|
5065
|
+
dataChannelState: this.channel?.readyState,
|
|
5066
|
+
diagnostics: this.diagnostics()
|
|
4937
5067
|
};
|
|
4938
5068
|
}
|
|
4939
5069
|
getStats() {
|
|
@@ -5029,13 +5159,19 @@ var RtcDataChannelTransport = class {
|
|
|
5029
5159
|
}
|
|
5030
5160
|
}
|
|
5031
5161
|
async handleIce(candidate) {
|
|
5162
|
+
const summary = summarizeIceCandidate(candidate);
|
|
5032
5163
|
if (!this.remoteDescriptionSet) {
|
|
5164
|
+
recordCandidate(this.remoteCandidateTelemetry, summary);
|
|
5165
|
+
this.emitDiagnostic({ type: "remote-candidate", candidate: summary, action: "buffered", diagnostics: this.diagnostics() });
|
|
5033
5166
|
this.remoteCandidates.push(candidate);
|
|
5034
5167
|
return;
|
|
5035
5168
|
}
|
|
5036
5169
|
try {
|
|
5037
5170
|
await this.pc.addIceCandidate(candidate);
|
|
5171
|
+
recordCandidate(this.remoteCandidateTelemetry, summary);
|
|
5172
|
+
this.emitDiagnostic({ type: "remote-candidate", candidate: summary, action: "added", diagnostics: this.diagnostics() });
|
|
5038
5173
|
} catch {
|
|
5174
|
+
this.emitDiagnostic({ type: "remote-candidate", candidate: summary, action: "ignored", diagnostics: this.diagnostics() });
|
|
5039
5175
|
}
|
|
5040
5176
|
}
|
|
5041
5177
|
async flushRemoteCandidates() {
|
|
@@ -5100,9 +5236,15 @@ var RtcDataChannelTransport = class {
|
|
|
5100
5236
|
}
|
|
5101
5237
|
async resolveSelectedTransport() {
|
|
5102
5238
|
try {
|
|
5103
|
-
const selected =
|
|
5239
|
+
const selected = await this.refreshStatsDiagnostics();
|
|
5104
5240
|
this.selected = selected?.transport;
|
|
5105
5241
|
this.selectedMode = selected?.mode;
|
|
5242
|
+
this.selectedPathTelemetry = selectedPathTelemetry(selected);
|
|
5243
|
+
this.emitDiagnostic({
|
|
5244
|
+
type: "selected-path",
|
|
5245
|
+
selectedPath: this.selectedPathTelemetry,
|
|
5246
|
+
diagnostics: this.diagnostics()
|
|
5247
|
+
});
|
|
5106
5248
|
} catch {
|
|
5107
5249
|
this.selected = void 0;
|
|
5108
5250
|
this.selectedMode = void 0;
|
|
@@ -5112,9 +5254,16 @@ var RtcDataChannelTransport = class {
|
|
|
5112
5254
|
if (this.closed || this.opened)
|
|
5113
5255
|
return;
|
|
5114
5256
|
if (this.pc.connectionState === "failed" || this.pc.iceConnectionState === "failed" || this.pc.connectionState === "closed" || this.pc.iceConnectionState === "closed") {
|
|
5115
|
-
this.
|
|
5257
|
+
void this.failOpenAfterStats(new RtcConnectError("RTC_FAILED", "WebRTC peer connection failed before the data channel opened."));
|
|
5116
5258
|
}
|
|
5117
5259
|
}
|
|
5260
|
+
async failOpenAfterStats(error) {
|
|
5261
|
+
await Promise.race([
|
|
5262
|
+
this.refreshStatsDiagnostics().catch(() => void 0),
|
|
5263
|
+
sleep(250)
|
|
5264
|
+
]);
|
|
5265
|
+
this.failOpen(error);
|
|
5266
|
+
}
|
|
5118
5267
|
failOpen(error) {
|
|
5119
5268
|
if (this.closed || this.opened || this.openReject === void 0)
|
|
5120
5269
|
return;
|
|
@@ -5176,6 +5325,46 @@ var RtcDataChannelTransport = class {
|
|
|
5176
5325
|
this.watchdogTimer = void 0;
|
|
5177
5326
|
this.lastBufferedAmount = 0;
|
|
5178
5327
|
}
|
|
5328
|
+
async refreshStatsDiagnostics() {
|
|
5329
|
+
const stats = [...await this.pc.getStats()];
|
|
5330
|
+
this.candidatePairTelemetry = inspectCandidatePairs(stats);
|
|
5331
|
+
return inspectSelectedPath(stats);
|
|
5332
|
+
}
|
|
5333
|
+
handlePeerDiagnostic(event) {
|
|
5334
|
+
if (event.type === "local-candidate-filtered") {
|
|
5335
|
+
recordCandidate(this.filteredLocalCandidateTelemetry, event.candidate);
|
|
5336
|
+
this.emitDiagnostic({
|
|
5337
|
+
type: event.type,
|
|
5338
|
+
candidate: event.candidate,
|
|
5339
|
+
reason: event.reason,
|
|
5340
|
+
diagnostics: this.diagnostics()
|
|
5341
|
+
});
|
|
5342
|
+
return;
|
|
5343
|
+
}
|
|
5344
|
+
if (event.localCandidate !== void 0)
|
|
5345
|
+
recordCandidate(this.filteredCandidatePairTelemetry, event.localCandidate);
|
|
5346
|
+
this.emitDiagnostic({
|
|
5347
|
+
type: event.type,
|
|
5348
|
+
localCandidate: event.localCandidate,
|
|
5349
|
+
reason: event.reason,
|
|
5350
|
+
diagnostics: this.diagnostics()
|
|
5351
|
+
});
|
|
5352
|
+
}
|
|
5353
|
+
emitStateChange() {
|
|
5354
|
+
this.emitDiagnostic({
|
|
5355
|
+
type: "state-change",
|
|
5356
|
+
connectionState: this.pc.connectionState,
|
|
5357
|
+
iceConnectionState: this.pc.iceConnectionState,
|
|
5358
|
+
iceGatheringState: this.pc.iceGatheringState,
|
|
5359
|
+
diagnostics: this.diagnostics()
|
|
5360
|
+
});
|
|
5361
|
+
}
|
|
5362
|
+
emitDiagnostic(event) {
|
|
5363
|
+
try {
|
|
5364
|
+
this.onDiagnostic?.(event);
|
|
5365
|
+
} catch {
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5179
5368
|
};
|
|
5180
5369
|
var RtcConnectError = class extends Error {
|
|
5181
5370
|
code;
|
|
@@ -5237,6 +5426,33 @@ function inspectSelectedPath(stats) {
|
|
|
5237
5426
|
}
|
|
5238
5427
|
return {};
|
|
5239
5428
|
}
|
|
5429
|
+
function inspectCandidatePairs(stats) {
|
|
5430
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
5431
|
+
const telemetry = emptyCandidatePairTelemetry();
|
|
5432
|
+
const pairs = [];
|
|
5433
|
+
for (const [reportId, entry] of stats) {
|
|
5434
|
+
if (entry.type === "local-candidate" || entry.type === "remote-candidate") {
|
|
5435
|
+
if (typeof entry.candidateType === "string" && entry.id !== void 0) {
|
|
5436
|
+
candidates.set(String(entry.id), entry);
|
|
5437
|
+
}
|
|
5438
|
+
} else if (entry.type === "candidate-pair") {
|
|
5439
|
+
pairs.push({ ...entry, id: entry.id ?? reportId });
|
|
5440
|
+
}
|
|
5441
|
+
}
|
|
5442
|
+
for (const pair of pairs) {
|
|
5443
|
+
telemetry.total += 1;
|
|
5444
|
+
if (pair.nominated === true)
|
|
5445
|
+
telemetry.nominated += 1;
|
|
5446
|
+
if (pair.selected === true)
|
|
5447
|
+
telemetry.selected += 1;
|
|
5448
|
+
increment(telemetry.byState, stringStat(pair, "state") ?? (pair.selected === true ? "selected" : "unknown"));
|
|
5449
|
+
const local = candidates.get(String(pair.localCandidateId));
|
|
5450
|
+
const remote = candidates.get(String(pair.remoteCandidateId));
|
|
5451
|
+
recordCandidateStats(telemetry.byLocalType, telemetry.byLocalFamily, telemetry.byLocalScope, local);
|
|
5452
|
+
recordCandidateStats(telemetry.byRemoteType, telemetry.byRemoteFamily, telemetry.byRemoteScope, remote);
|
|
5453
|
+
}
|
|
5454
|
+
return telemetry;
|
|
5455
|
+
}
|
|
5240
5456
|
function candidateAddress(candidate) {
|
|
5241
5457
|
const address = stringStat(candidate, "address") ?? stringStat(candidate, "ip");
|
|
5242
5458
|
if (address === void 0)
|
|
@@ -5252,6 +5468,91 @@ function numberStat(entry, key) {
|
|
|
5252
5468
|
const value = entry?.[key];
|
|
5253
5469
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5254
5470
|
}
|
|
5471
|
+
function emptyCandidateTelemetry() {
|
|
5472
|
+
return {
|
|
5473
|
+
total: 0,
|
|
5474
|
+
byType: {},
|
|
5475
|
+
byFamily: {},
|
|
5476
|
+
byScope: {},
|
|
5477
|
+
byProtocol: {}
|
|
5478
|
+
};
|
|
5479
|
+
}
|
|
5480
|
+
function cloneCandidateTelemetry(value) {
|
|
5481
|
+
return {
|
|
5482
|
+
total: value.total,
|
|
5483
|
+
byType: { ...value.byType },
|
|
5484
|
+
byFamily: { ...value.byFamily },
|
|
5485
|
+
byScope: { ...value.byScope },
|
|
5486
|
+
byProtocol: { ...value.byProtocol }
|
|
5487
|
+
};
|
|
5488
|
+
}
|
|
5489
|
+
function emptyCandidatePairTelemetry() {
|
|
5490
|
+
return {
|
|
5491
|
+
total: 0,
|
|
5492
|
+
nominated: 0,
|
|
5493
|
+
selected: 0,
|
|
5494
|
+
byState: {},
|
|
5495
|
+
byLocalType: {},
|
|
5496
|
+
byRemoteType: {},
|
|
5497
|
+
byLocalFamily: {},
|
|
5498
|
+
byRemoteFamily: {},
|
|
5499
|
+
byLocalScope: {},
|
|
5500
|
+
byRemoteScope: {}
|
|
5501
|
+
};
|
|
5502
|
+
}
|
|
5503
|
+
function cloneCandidatePairTelemetry(value) {
|
|
5504
|
+
return {
|
|
5505
|
+
total: value.total,
|
|
5506
|
+
nominated: value.nominated,
|
|
5507
|
+
selected: value.selected,
|
|
5508
|
+
byState: { ...value.byState },
|
|
5509
|
+
byLocalType: { ...value.byLocalType },
|
|
5510
|
+
byRemoteType: { ...value.byRemoteType },
|
|
5511
|
+
byLocalFamily: { ...value.byLocalFamily },
|
|
5512
|
+
byRemoteFamily: { ...value.byRemoteFamily },
|
|
5513
|
+
byLocalScope: { ...value.byLocalScope },
|
|
5514
|
+
byRemoteScope: { ...value.byRemoteScope }
|
|
5515
|
+
};
|
|
5516
|
+
}
|
|
5517
|
+
function recordCandidate(telemetry, candidate) {
|
|
5518
|
+
telemetry.total += 1;
|
|
5519
|
+
increment(telemetry.byType, candidate.candidateType);
|
|
5520
|
+
increment(telemetry.byFamily, candidate.addressFamily);
|
|
5521
|
+
increment(telemetry.byScope, candidate.addressScope);
|
|
5522
|
+
if (candidate.protocol !== void 0)
|
|
5523
|
+
increment(telemetry.byProtocol, candidate.protocol);
|
|
5524
|
+
}
|
|
5525
|
+
function recordCandidateStats(byType, byFamily, byScope, candidate) {
|
|
5526
|
+
const candidateType = candidateTypeStat(candidate);
|
|
5527
|
+
const address = summarizeAddress(candidateAddressValue(candidate));
|
|
5528
|
+
increment(byType, candidateType);
|
|
5529
|
+
increment(byFamily, address.addressFamily);
|
|
5530
|
+
increment(byScope, address.addressScope);
|
|
5531
|
+
}
|
|
5532
|
+
function candidateTypeStat(candidate) {
|
|
5533
|
+
const value = stringStat(candidate, "candidateType");
|
|
5534
|
+
if (value === "host" || value === "srflx" || value === "prflx" || value === "relay")
|
|
5535
|
+
return value;
|
|
5536
|
+
return "unknown";
|
|
5537
|
+
}
|
|
5538
|
+
function candidateAddressValue(candidate) {
|
|
5539
|
+
return stringStat(candidate, "address") ?? stringStat(candidate, "ip");
|
|
5540
|
+
}
|
|
5541
|
+
function increment(map, key) {
|
|
5542
|
+
map[key] = (map[key] ?? 0) + 1;
|
|
5543
|
+
}
|
|
5544
|
+
function selectedPathTelemetry(details) {
|
|
5545
|
+
return {
|
|
5546
|
+
...details.transport === void 0 ? {} : { transport: details.transport },
|
|
5547
|
+
...details.mode === void 0 ? {} : { mode: details.mode },
|
|
5548
|
+
...details.localCandidateType === void 0 ? {} : { localCandidateType: details.localCandidateType },
|
|
5549
|
+
...details.remoteCandidateType === void 0 ? {} : { remoteCandidateType: details.remoteCandidateType },
|
|
5550
|
+
...details.protocol === void 0 ? {} : { protocol: details.protocol },
|
|
5551
|
+
...details.relayProtocol === void 0 ? {} : { relayProtocol: details.relayProtocol },
|
|
5552
|
+
...details.currentRoundTripTimeMs === void 0 ? {} : { currentRoundTripTimeMs: details.currentRoundTripTimeMs },
|
|
5553
|
+
...details.availableOutgoingBitrate === void 0 ? {} : { availableOutgoingBitrate: details.availableOutgoingBitrate }
|
|
5554
|
+
};
|
|
5555
|
+
}
|
|
5255
5556
|
function requireSdp(description) {
|
|
5256
5557
|
if (typeof description.sdp !== "string" || description.sdp.length === 0) {
|
|
5257
5558
|
throw new RtcConnectError("RTC_FAILED", "The RTC backend produced an empty session description.");
|
|
@@ -5266,6 +5567,9 @@ function toArrayBuffer(data) {
|
|
|
5266
5567
|
function asError(error) {
|
|
5267
5568
|
return error instanceof Error ? error : new RtcConnectError("RTC_FAILED", "WebRTC negotiation failed.");
|
|
5268
5569
|
}
|
|
5570
|
+
function sleep(ms) {
|
|
5571
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
5572
|
+
}
|
|
5269
5573
|
|
|
5270
5574
|
// ../webrtc/dist/adaptive-transport.js
|
|
5271
5575
|
var DEFAULT_CAPABILITIES = ["transport.p2p", "transport.turn", "transport.relay", "harness.api.v1"];
|
|
@@ -5288,6 +5592,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5288
5592
|
webrtcEnabled = true;
|
|
5289
5593
|
serverNegotiateTimeoutMs;
|
|
5290
5594
|
connectedAt;
|
|
5595
|
+
lastRtcDiagnostics;
|
|
5291
5596
|
constructor(url, options) {
|
|
5292
5597
|
super();
|
|
5293
5598
|
this.url = url;
|
|
@@ -5487,6 +5792,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5487
5792
|
const preferred = this.options.preferredTransports ?? [...DEFAULT_PREFERRED_TRANSPORTS];
|
|
5488
5793
|
const wantWebRtc = !this.options.forceRelay && this.webrtcEnabled && (preferred.includes("lan") || preferred.includes("p2p") || preferred.includes("turn"));
|
|
5489
5794
|
let selected = "relay";
|
|
5795
|
+
this.lastRtcDiagnostics = void 0;
|
|
5490
5796
|
if (wantWebRtc) {
|
|
5491
5797
|
try {
|
|
5492
5798
|
const rtcSelected = await this.tryWebRtc();
|
|
@@ -5495,7 +5801,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5495
5801
|
} catch (error) {
|
|
5496
5802
|
const reason = error instanceof Error ? error : new Error("WebRTC negotiation failed.");
|
|
5497
5803
|
try {
|
|
5498
|
-
this.options.onWebRtcFallback?.(reason);
|
|
5804
|
+
this.options.onWebRtcFallback?.(reason, this.lastRtcDiagnostics);
|
|
5499
5805
|
} catch {
|
|
5500
5806
|
}
|
|
5501
5807
|
await this.rtc?.close();
|
|
@@ -5534,8 +5840,15 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5534
5840
|
factory,
|
|
5535
5841
|
iceServers,
|
|
5536
5842
|
onSignal: (signal) => this.sendRtcSignal(signal),
|
|
5537
|
-
negotiateTimeoutMs: this.serverNegotiateTimeoutMs
|
|
5538
|
-
label: `client->${this.options.targetDeviceId}
|
|
5843
|
+
negotiateTimeoutMs: negotiateTimeout(this.serverNegotiateTimeoutMs, this.options.negotiateTimeoutMs),
|
|
5844
|
+
label: `client->${this.options.targetDeviceId}`,
|
|
5845
|
+
onDiagnostic: (event) => {
|
|
5846
|
+
this.lastRtcDiagnostics = event.diagnostics;
|
|
5847
|
+
try {
|
|
5848
|
+
this.options.onWebRtcDiagnostic?.(event);
|
|
5849
|
+
} catch {
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5539
5852
|
});
|
|
5540
5853
|
this.rtc = rtc;
|
|
5541
5854
|
rtc.onMessage((data) => {
|
|
@@ -5549,10 +5862,12 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5549
5862
|
try {
|
|
5550
5863
|
await rtc.connect();
|
|
5551
5864
|
} catch (error) {
|
|
5865
|
+
this.lastRtcDiagnostics = rtc.diagnostics();
|
|
5552
5866
|
await rtc.close();
|
|
5553
5867
|
this.rtc = void 0;
|
|
5554
5868
|
throw error;
|
|
5555
5869
|
}
|
|
5870
|
+
this.lastRtcDiagnostics = rtc.diagnostics();
|
|
5556
5871
|
return rtc.selectedTransport() ?? "p2p";
|
|
5557
5872
|
}
|
|
5558
5873
|
sendRtcSignal(signal) {
|
|
@@ -5625,6 +5940,13 @@ function socketState(readyState) {
|
|
|
5625
5940
|
return "closing";
|
|
5626
5941
|
return "closed";
|
|
5627
5942
|
}
|
|
5943
|
+
function negotiateTimeout(serverMs, localMs) {
|
|
5944
|
+
if (serverMs === void 0)
|
|
5945
|
+
return localMs;
|
|
5946
|
+
if (localMs === void 0)
|
|
5947
|
+
return serverMs;
|
|
5948
|
+
return Math.max(serverMs, localMs);
|
|
5949
|
+
}
|
|
5628
5950
|
|
|
5629
5951
|
// src/api-proxy-switch.ts
|
|
5630
5952
|
var SWITCHED_DOMAINS = [
|
|
@@ -13212,7 +13534,7 @@ function normalizeServerUrl(value) {
|
|
|
13212
13534
|
}
|
|
13213
13535
|
|
|
13214
13536
|
// src/version.ts
|
|
13215
|
-
var PLUGIN_VERSION = "0.3.
|
|
13537
|
+
var PLUGIN_VERSION = "0.3.34";
|
|
13216
13538
|
|
|
13217
13539
|
// src/server-api.ts
|
|
13218
13540
|
var HostServerApi = class {
|
|
@@ -13651,33 +13973,662 @@ function isRemoteCommandMethod(method) {
|
|
|
13651
13973
|
}
|
|
13652
13974
|
|
|
13653
13975
|
// src/werift-rtc.ts
|
|
13976
|
+
import { createSocket } from "node:dgram";
|
|
13654
13977
|
import { networkInterfaces } from "node:os";
|
|
13978
|
+
|
|
13979
|
+
// src/native-rtc-helper.ts
|
|
13980
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
13981
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
13982
|
+
import { delimiter, dirname, join } from "node:path";
|
|
13983
|
+
import { fileURLToPath } from "node:url";
|
|
13984
|
+
var cachedExternalFactory;
|
|
13985
|
+
var cachedExternalFactoryResolved = false;
|
|
13986
|
+
var cachedNodeBinary;
|
|
13987
|
+
var cachedNodeBinaryResolved = false;
|
|
13988
|
+
async function loadExternalNativeRtcFactory() {
|
|
13989
|
+
if (cachedExternalFactory !== void 0) return cachedExternalFactory;
|
|
13990
|
+
if (cachedExternalFactoryResolved) return void 0;
|
|
13991
|
+
cachedExternalFactoryResolved = true;
|
|
13992
|
+
const nodeBinary = resolveExternalNodeBinaryForRtc();
|
|
13993
|
+
if (nodeBinary === void 0) return void 0;
|
|
13994
|
+
cachedExternalFactory = buildExternalNativeRtcFactory(nodeBinary);
|
|
13995
|
+
return cachedExternalFactory;
|
|
13996
|
+
}
|
|
13997
|
+
function buildExternalNativeRtcFactory(nodeBinary, requireFrom = fileURLToPath(import.meta.url)) {
|
|
13998
|
+
return {
|
|
13999
|
+
create(configuration) {
|
|
14000
|
+
return new ExternalNativePeerConnection(nodeBinary, requireFrom, configuration);
|
|
14001
|
+
}
|
|
14002
|
+
};
|
|
14003
|
+
}
|
|
14004
|
+
function resolveExternalNodeBinaryForRtc() {
|
|
14005
|
+
if (cachedNodeBinaryResolved) return cachedNodeBinary;
|
|
14006
|
+
cachedNodeBinaryResolved = true;
|
|
14007
|
+
const requireFrom = fileURLToPath(import.meta.url);
|
|
14008
|
+
for (const candidate of nodeBinaryCandidates()) {
|
|
14009
|
+
if (isUsableExternalNode(candidate, requireFrom)) {
|
|
14010
|
+
cachedNodeBinary = candidate;
|
|
14011
|
+
return candidate;
|
|
14012
|
+
}
|
|
14013
|
+
}
|
|
14014
|
+
return void 0;
|
|
14015
|
+
}
|
|
14016
|
+
function nodeBinaryCandidates() {
|
|
14017
|
+
const candidates = [];
|
|
14018
|
+
const add3 = (value) => {
|
|
14019
|
+
if (value === void 0 || value.trim() === "") return;
|
|
14020
|
+
if (!candidates.includes(value)) candidates.push(value);
|
|
14021
|
+
};
|
|
14022
|
+
add3(process.env.DSH_REMOTE_NODE);
|
|
14023
|
+
add3(process.env.NODE);
|
|
14024
|
+
add3(process.execPath);
|
|
14025
|
+
for (const part of (process.env.PATH ?? "").split(delimiter)) add3(join(part, process.platform === "win32" ? "node.exe" : "node"));
|
|
14026
|
+
add3("/opt/homebrew/bin/node");
|
|
14027
|
+
add3("/usr/local/bin/node");
|
|
14028
|
+
add3("/usr/bin/node");
|
|
14029
|
+
add3(join(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
14030
|
+
add3(join(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
|
|
14031
|
+
add3(join(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
14032
|
+
for (const nvmNode of nvmNodeCandidates()) add3(nvmNode);
|
|
14033
|
+
return candidates;
|
|
14034
|
+
}
|
|
14035
|
+
function nvmNodeCandidates() {
|
|
14036
|
+
const root = join(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
14037
|
+
if (root === "" || !existsSync(root)) return [];
|
|
14038
|
+
try {
|
|
14039
|
+
return readdirSync(root).map((version) => join(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
|
|
14040
|
+
} catch {
|
|
14041
|
+
return [];
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14044
|
+
function isUsableExternalNode(candidate, requireFrom) {
|
|
14045
|
+
if (!isExecutableFile(candidate)) return false;
|
|
14046
|
+
const env = { ...process.env, DSH_REMOTE_RTC_HELPER_REQUIRE_FROM: requireFrom };
|
|
14047
|
+
delete env.ELECTRON_RUN_AS_NODE;
|
|
14048
|
+
const probe = spawnSync(candidate, [
|
|
14049
|
+
"--input-type=module",
|
|
14050
|
+
"--eval",
|
|
14051
|
+
[
|
|
14052
|
+
"import { createRequire } from 'node:module';",
|
|
14053
|
+
"const require = createRequire(process.env.DSH_REMOTE_RTC_HELPER_REQUIRE_FROM);",
|
|
14054
|
+
"if (process.versions.electron) throw new Error('electron-node');",
|
|
14055
|
+
"const wrtc = require('@roamhq/wrtc');",
|
|
14056
|
+
"const RTCPeerConnection = wrtc.RTCPeerConnection ?? wrtc.default?.RTCPeerConnection;",
|
|
14057
|
+
"if (typeof RTCPeerConnection !== 'function') throw new Error('missing-rtc');",
|
|
14058
|
+
"console.log(`node:${process.versions.node}`);"
|
|
14059
|
+
].join("")
|
|
14060
|
+
], {
|
|
14061
|
+
cwd: dirname(requireFrom),
|
|
14062
|
+
env,
|
|
14063
|
+
encoding: "utf8",
|
|
14064
|
+
timeout: 3e3
|
|
14065
|
+
});
|
|
14066
|
+
return probe.status === 0 && probe.stdout.startsWith("node:");
|
|
14067
|
+
}
|
|
14068
|
+
function isExecutableFile(candidate) {
|
|
14069
|
+
try {
|
|
14070
|
+
const stat4 = statSync(candidate);
|
|
14071
|
+
if (!stat4.isFile()) return false;
|
|
14072
|
+
if (process.platform === "win32") return true;
|
|
14073
|
+
return (stat4.mode & 73) !== 0;
|
|
14074
|
+
} catch {
|
|
14075
|
+
return false;
|
|
14076
|
+
}
|
|
14077
|
+
}
|
|
14078
|
+
var ExternalNativePeerConnection = class {
|
|
14079
|
+
constructor(nodeBinary, requireFrom, configuration) {
|
|
14080
|
+
this.nodeBinary = nodeBinary;
|
|
14081
|
+
this.requireFrom = requireFrom;
|
|
14082
|
+
this.child = spawn(this.nodeBinary, ["--input-type=module", "--eval", HELPER_SOURCE], {
|
|
14083
|
+
cwd: dirname(this.requireFrom),
|
|
14084
|
+
env: { ...process.env, DSH_REMOTE_RTC_HELPER_REQUIRE_FROM: this.requireFrom },
|
|
14085
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
14086
|
+
});
|
|
14087
|
+
this.child.stdout.setEncoding("utf8");
|
|
14088
|
+
this.child.stdout.on("data", (chunk) => this.handleStdout(String(chunk)));
|
|
14089
|
+
this.child.stderr.on("data", () => void 0);
|
|
14090
|
+
this.child.on("exit", () => this.handleExit());
|
|
14091
|
+
this.child.on("error", (error) => this.handleExit(error));
|
|
14092
|
+
this.child.stdin.on("error", (error) => this.handleExit(error));
|
|
14093
|
+
this.ready = this.request("init", { iceServers: configuration.iceServers ?? [] }).then(() => void 0);
|
|
14094
|
+
}
|
|
14095
|
+
connectionState = "new";
|
|
14096
|
+
iceConnectionState = "new";
|
|
14097
|
+
iceGatheringState = "new";
|
|
14098
|
+
signalingState = "stable";
|
|
14099
|
+
onconnectionstatechange = null;
|
|
14100
|
+
oniceconnectionstatechange = null;
|
|
14101
|
+
onicegatheringstatechange = null;
|
|
14102
|
+
onicecandidate = null;
|
|
14103
|
+
ondatachannel = null;
|
|
14104
|
+
child;
|
|
14105
|
+
pending = /* @__PURE__ */ new Map();
|
|
14106
|
+
channels = /* @__PURE__ */ new Map();
|
|
14107
|
+
nextRequestId = 1;
|
|
14108
|
+
nextChannelId = 1;
|
|
14109
|
+
stdout = "";
|
|
14110
|
+
closed = false;
|
|
14111
|
+
ready;
|
|
14112
|
+
createDataChannel(label, options) {
|
|
14113
|
+
const channelId = this.nextChannelId++;
|
|
14114
|
+
const channel = new ExternalNativeDataChannel(this, channelId, label, options?.ordered ?? true);
|
|
14115
|
+
this.channels.set(channelId, channel);
|
|
14116
|
+
this.notify("createDataChannel", { channelId, label, options: { ordered: options?.ordered ?? true } });
|
|
14117
|
+
return channel;
|
|
14118
|
+
}
|
|
14119
|
+
async createOffer() {
|
|
14120
|
+
await this.ready;
|
|
14121
|
+
return this.request("createOffer", {});
|
|
14122
|
+
}
|
|
14123
|
+
async createAnswer() {
|
|
14124
|
+
await this.ready;
|
|
14125
|
+
return this.request("createAnswer", {});
|
|
14126
|
+
}
|
|
14127
|
+
async setLocalDescription(description) {
|
|
14128
|
+
await this.ready;
|
|
14129
|
+
await this.request("setLocalDescription", { description });
|
|
14130
|
+
}
|
|
14131
|
+
async setRemoteDescription(description) {
|
|
14132
|
+
await this.ready;
|
|
14133
|
+
await this.request("setRemoteDescription", { description });
|
|
14134
|
+
}
|
|
14135
|
+
async addIceCandidate(candidate) {
|
|
14136
|
+
await this.ready;
|
|
14137
|
+
await this.request("addIceCandidate", { candidate: candidate ?? null });
|
|
14138
|
+
}
|
|
14139
|
+
async getStats() {
|
|
14140
|
+
await this.ready;
|
|
14141
|
+
return this.request("getStats", {});
|
|
14142
|
+
}
|
|
14143
|
+
close() {
|
|
14144
|
+
if (this.closed) return;
|
|
14145
|
+
this.closed = true;
|
|
14146
|
+
this.notify("close", {});
|
|
14147
|
+
const timer = setTimeout(() => {
|
|
14148
|
+
if (this.child.exitCode === null && this.child.signalCode === null) this.child.kill();
|
|
14149
|
+
}, 1e3);
|
|
14150
|
+
timer.unref?.();
|
|
14151
|
+
}
|
|
14152
|
+
sendChannelData(channelId, data) {
|
|
14153
|
+
const payload = typeof data === "string" ? { channelId, text: data } : { channelId, base64: Buffer.from(data).toString("base64") };
|
|
14154
|
+
this.notify("channelSend", payload);
|
|
14155
|
+
}
|
|
14156
|
+
closeChannel(channelId) {
|
|
14157
|
+
this.notify("channelClose", { channelId });
|
|
14158
|
+
}
|
|
14159
|
+
request(method, payload) {
|
|
14160
|
+
if (this.closed) return Promise.reject(new Error("native rtc helper is closed"));
|
|
14161
|
+
const id = this.nextRequestId++;
|
|
14162
|
+
const promise = new Promise((resolve2, reject) => {
|
|
14163
|
+
this.pending.set(id, { resolve: resolve2, reject });
|
|
14164
|
+
});
|
|
14165
|
+
this.write({ id, method, payload });
|
|
14166
|
+
return promise;
|
|
14167
|
+
}
|
|
14168
|
+
notify(method, payload) {
|
|
14169
|
+
if (this.closed && method !== "close") return;
|
|
14170
|
+
this.write({ method, payload });
|
|
14171
|
+
}
|
|
14172
|
+
write(message) {
|
|
14173
|
+
try {
|
|
14174
|
+
this.child.stdin.write(`${JSON.stringify(message)}
|
|
14175
|
+
`);
|
|
14176
|
+
} catch {
|
|
14177
|
+
this.handleExit(new Error("native rtc helper stdin is closed"));
|
|
14178
|
+
}
|
|
14179
|
+
}
|
|
14180
|
+
handleStdout(chunk) {
|
|
14181
|
+
this.stdout += chunk;
|
|
14182
|
+
for (; ; ) {
|
|
14183
|
+
const newline = this.stdout.indexOf("\n");
|
|
14184
|
+
if (newline < 0) return;
|
|
14185
|
+
const line = this.stdout.slice(0, newline);
|
|
14186
|
+
this.stdout = this.stdout.slice(newline + 1);
|
|
14187
|
+
if (line.trim() === "") continue;
|
|
14188
|
+
try {
|
|
14189
|
+
this.handleMessage(JSON.parse(line));
|
|
14190
|
+
} catch {
|
|
14191
|
+
this.handleExit(new Error("native rtc helper sent invalid JSON"));
|
|
14192
|
+
}
|
|
14193
|
+
}
|
|
14194
|
+
}
|
|
14195
|
+
handleMessage(message) {
|
|
14196
|
+
if ("id" in message) {
|
|
14197
|
+
const pending = this.pending.get(message.id);
|
|
14198
|
+
if (pending === void 0) return;
|
|
14199
|
+
this.pending.delete(message.id);
|
|
14200
|
+
if (message.ok) pending.resolve(message.value);
|
|
14201
|
+
else pending.reject(new Error(message.error ?? "native rtc helper command failed"));
|
|
14202
|
+
return;
|
|
14203
|
+
}
|
|
14204
|
+
if (message.event === "state") {
|
|
14205
|
+
this.connectionState = message.connectionState;
|
|
14206
|
+
this.iceConnectionState = message.iceConnectionState;
|
|
14207
|
+
this.iceGatheringState = message.iceGatheringState;
|
|
14208
|
+
this.signalingState = message.signalingState;
|
|
14209
|
+
this.onconnectionstatechange?.();
|
|
14210
|
+
this.oniceconnectionstatechange?.();
|
|
14211
|
+
this.onicegatheringstatechange?.();
|
|
14212
|
+
return;
|
|
14213
|
+
}
|
|
14214
|
+
if (message.event === "icecandidate") {
|
|
14215
|
+
this.onicecandidate?.({ candidate: message.candidate });
|
|
14216
|
+
return;
|
|
14217
|
+
}
|
|
14218
|
+
if (message.event === "datachannel") {
|
|
14219
|
+
const channel = this.channelFor(message.channelId, message.label, message.ordered);
|
|
14220
|
+
this.ondatachannel?.({ channel });
|
|
14221
|
+
channel.updateState(message.readyState, message.bufferedAmount);
|
|
14222
|
+
return;
|
|
14223
|
+
}
|
|
14224
|
+
if (message.event === "channel-state") {
|
|
14225
|
+
this.channels.get(message.channelId)?.updateState(message.readyState, message.bufferedAmount);
|
|
14226
|
+
return;
|
|
14227
|
+
}
|
|
14228
|
+
if (message.event === "channel-message") {
|
|
14229
|
+
const channel = this.channels.get(message.channelId);
|
|
14230
|
+
if (channel === void 0) return;
|
|
14231
|
+
if (message.text !== void 0) channel.emitMessage(message.text);
|
|
14232
|
+
else if (message.base64 !== void 0) channel.emitMessage(bufferToArrayBuffer(Buffer.from(message.base64, "base64")));
|
|
14233
|
+
return;
|
|
14234
|
+
}
|
|
14235
|
+
if (message.event === "channel-error") {
|
|
14236
|
+
this.channels.get(message.channelId)?.emitError();
|
|
14237
|
+
}
|
|
14238
|
+
}
|
|
14239
|
+
channelFor(channelId, label, ordered) {
|
|
14240
|
+
const existing = this.channels.get(channelId);
|
|
14241
|
+
if (existing !== void 0) return existing;
|
|
14242
|
+
const channel = new ExternalNativeDataChannel(this, channelId, label, ordered);
|
|
14243
|
+
this.channels.set(channelId, channel);
|
|
14244
|
+
return channel;
|
|
14245
|
+
}
|
|
14246
|
+
handleExit(error) {
|
|
14247
|
+
if (!this.closed) this.closed = true;
|
|
14248
|
+
const reason = error ?? new Error("native rtc helper exited");
|
|
14249
|
+
for (const pending of this.pending.values()) pending.reject(reason);
|
|
14250
|
+
this.pending.clear();
|
|
14251
|
+
for (const channel of this.channels.values()) channel.updateState("closed", 0);
|
|
14252
|
+
}
|
|
14253
|
+
};
|
|
14254
|
+
var ExternalNativeDataChannel = class {
|
|
14255
|
+
constructor(owner, channelId, label, ordered) {
|
|
14256
|
+
this.owner = owner;
|
|
14257
|
+
this.channelId = channelId;
|
|
14258
|
+
this.label = label;
|
|
14259
|
+
this.ordered = ordered;
|
|
14260
|
+
}
|
|
14261
|
+
readyState = "connecting";
|
|
14262
|
+
bufferedAmount = 0;
|
|
14263
|
+
binaryType = "arraybuffer";
|
|
14264
|
+
onopen = null;
|
|
14265
|
+
onmessage = null;
|
|
14266
|
+
onclose = null;
|
|
14267
|
+
onerror = null;
|
|
14268
|
+
onbufferedamountlow = null;
|
|
14269
|
+
send(data) {
|
|
14270
|
+
this.owner.sendChannelData(this.channelId, data);
|
|
14271
|
+
}
|
|
14272
|
+
close() {
|
|
14273
|
+
this.owner.closeChannel(this.channelId);
|
|
14274
|
+
}
|
|
14275
|
+
updateState(readyState, bufferedAmount) {
|
|
14276
|
+
const previous = this.readyState;
|
|
14277
|
+
this.readyState = readyState;
|
|
14278
|
+
this.bufferedAmount = bufferedAmount;
|
|
14279
|
+
if (previous !== "open" && readyState === "open") this.onopen?.();
|
|
14280
|
+
if (previous !== "closed" && readyState === "closed") this.onclose?.();
|
|
14281
|
+
if (bufferedAmount === 0) this.onbufferedamountlow?.();
|
|
14282
|
+
}
|
|
14283
|
+
emitMessage(data) {
|
|
14284
|
+
this.onmessage?.({ data });
|
|
14285
|
+
}
|
|
14286
|
+
emitError() {
|
|
14287
|
+
this.onerror?.();
|
|
14288
|
+
}
|
|
14289
|
+
};
|
|
14290
|
+
function bufferToArrayBuffer(buffer) {
|
|
14291
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
14292
|
+
}
|
|
14293
|
+
var HELPER_SOURCE = String.raw`
|
|
14294
|
+
import { createRequire } from 'node:module'
|
|
14295
|
+
import { createInterface } from 'node:readline'
|
|
14296
|
+
|
|
14297
|
+
const require = createRequire(process.env.DSH_REMOTE_RTC_HELPER_REQUIRE_FROM || (process.cwd() + '/package.json'))
|
|
14298
|
+
const wrtc = require('@roamhq/wrtc')
|
|
14299
|
+
const RTCPeerConnection = wrtc.RTCPeerConnection ?? wrtc.default?.RTCPeerConnection
|
|
14300
|
+
if (typeof RTCPeerConnection !== 'function') throw new Error('missing RTCPeerConnection')
|
|
14301
|
+
|
|
14302
|
+
let pc
|
|
14303
|
+
let nextRemoteChannelId = 100000
|
|
14304
|
+
const channels = new Map()
|
|
14305
|
+
let chain = Promise.resolve()
|
|
14306
|
+
|
|
14307
|
+
function send(message) {
|
|
14308
|
+
process.stdout.write(JSON.stringify(message) + '\n')
|
|
14309
|
+
}
|
|
14310
|
+
|
|
14311
|
+
function respond(id, ok, value, error) {
|
|
14312
|
+
if (id === undefined) return
|
|
14313
|
+
send(ok ? { id, ok: true, value } : { id, ok: false, error: String(error?.message ?? error ?? 'command failed') })
|
|
14314
|
+
}
|
|
14315
|
+
|
|
14316
|
+
function state() {
|
|
14317
|
+
return {
|
|
14318
|
+
event: 'state',
|
|
14319
|
+
connectionState: pc?.connectionState ?? 'closed',
|
|
14320
|
+
iceConnectionState: pc?.iceConnectionState ?? 'closed',
|
|
14321
|
+
iceGatheringState: pc?.iceGatheringState ?? 'complete',
|
|
14322
|
+
signalingState: pc?.signalingState ?? 'closed',
|
|
14323
|
+
}
|
|
14324
|
+
}
|
|
14325
|
+
|
|
14326
|
+
function emitState() {
|
|
14327
|
+
send(state())
|
|
14328
|
+
}
|
|
14329
|
+
|
|
14330
|
+
function normalizeCandidate(candidate) {
|
|
14331
|
+
if (candidate === null || candidate === undefined) return null
|
|
14332
|
+
if (typeof candidate.toJSON === 'function') return candidate.toJSON()
|
|
14333
|
+
return {
|
|
14334
|
+
candidate: candidate.candidate,
|
|
14335
|
+
sdpMid: candidate.sdpMid,
|
|
14336
|
+
sdpMLineIndex: candidate.sdpMLineIndex,
|
|
14337
|
+
usernameFragment: candidate.usernameFragment,
|
|
14338
|
+
}
|
|
14339
|
+
}
|
|
14340
|
+
|
|
14341
|
+
function attachChannel(channelId, channel) {
|
|
14342
|
+
channels.set(channelId, channel)
|
|
14343
|
+
try { channel.binaryType = 'arraybuffer' } catch {}
|
|
14344
|
+
const emitChannel = () => send({
|
|
14345
|
+
event: 'channel-state',
|
|
14346
|
+
channelId,
|
|
14347
|
+
readyState: channel.readyState,
|
|
14348
|
+
bufferedAmount: channel.bufferedAmount ?? 0,
|
|
14349
|
+
})
|
|
14350
|
+
channel.onopen = emitChannel
|
|
14351
|
+
channel.onclose = emitChannel
|
|
14352
|
+
channel.onerror = () => send({ event: 'channel-error', channelId })
|
|
14353
|
+
channel.onmessage = event => {
|
|
14354
|
+
const data = event.data
|
|
14355
|
+
if (typeof data === 'string') {
|
|
14356
|
+
send({ event: 'channel-message', channelId, text: data })
|
|
14357
|
+
return
|
|
14358
|
+
}
|
|
14359
|
+
send({ event: 'channel-message', channelId, base64: Buffer.from(data).toString('base64') })
|
|
14360
|
+
}
|
|
14361
|
+
send({
|
|
14362
|
+
event: 'datachannel',
|
|
14363
|
+
channelId,
|
|
14364
|
+
label: channel.label,
|
|
14365
|
+
ordered: channel.ordered,
|
|
14366
|
+
readyState: channel.readyState,
|
|
14367
|
+
bufferedAmount: channel.bufferedAmount ?? 0,
|
|
14368
|
+
})
|
|
14369
|
+
}
|
|
14370
|
+
|
|
14371
|
+
function serializeStats(stats) {
|
|
14372
|
+
const entries = []
|
|
14373
|
+
const push = (key, value) => {
|
|
14374
|
+
const copy = {}
|
|
14375
|
+
for (const [entryKey, entryValue] of Object.entries(value ?? {})) {
|
|
14376
|
+
if (typeof entryValue !== 'function') copy[entryKey] = entryValue
|
|
14377
|
+
}
|
|
14378
|
+
entries.push([String(key), copy])
|
|
14379
|
+
}
|
|
14380
|
+
if (typeof stats?.forEach === 'function') stats.forEach((value, key) => push(key, value))
|
|
14381
|
+
else for (const [key, value] of stats ?? []) push(key, value)
|
|
14382
|
+
return entries
|
|
14383
|
+
}
|
|
14384
|
+
|
|
14385
|
+
async function handle(command) {
|
|
14386
|
+
const { id, method, payload = {} } = command
|
|
14387
|
+
try {
|
|
14388
|
+
if (method === 'init') {
|
|
14389
|
+
pc = new RTCPeerConnection({ iceServers: payload.iceServers ?? [] })
|
|
14390
|
+
pc.onconnectionstatechange = emitState
|
|
14391
|
+
pc.oniceconnectionstatechange = emitState
|
|
14392
|
+
pc.onicegatheringstatechange = emitState
|
|
14393
|
+
pc.onsignalingstatechange = emitState
|
|
14394
|
+
pc.onicecandidate = event => send({ event: 'icecandidate', candidate: normalizeCandidate(event.candidate) })
|
|
14395
|
+
pc.ondatachannel = event => attachChannel(nextRemoteChannelId++, event.channel)
|
|
14396
|
+
emitState()
|
|
14397
|
+
respond(id, true, {})
|
|
14398
|
+
return
|
|
14399
|
+
}
|
|
14400
|
+
if (method === 'createDataChannel') {
|
|
14401
|
+
const channel = pc.createDataChannel(payload.label, payload.options)
|
|
14402
|
+
attachChannel(payload.channelId, channel)
|
|
14403
|
+
respond(id, true, {})
|
|
14404
|
+
return
|
|
14405
|
+
}
|
|
14406
|
+
if (method === 'createOffer') {
|
|
14407
|
+
respond(id, true, await pc.createOffer())
|
|
14408
|
+
return
|
|
14409
|
+
}
|
|
14410
|
+
if (method === 'createAnswer') {
|
|
14411
|
+
respond(id, true, await pc.createAnswer())
|
|
14412
|
+
return
|
|
14413
|
+
}
|
|
14414
|
+
if (method === 'setLocalDescription') {
|
|
14415
|
+
await pc.setLocalDescription(payload.description)
|
|
14416
|
+
emitState()
|
|
14417
|
+
respond(id, true, {})
|
|
14418
|
+
return
|
|
14419
|
+
}
|
|
14420
|
+
if (method === 'setRemoteDescription') {
|
|
14421
|
+
await pc.setRemoteDescription(payload.description)
|
|
14422
|
+
emitState()
|
|
14423
|
+
respond(id, true, {})
|
|
14424
|
+
return
|
|
14425
|
+
}
|
|
14426
|
+
if (method === 'addIceCandidate') {
|
|
14427
|
+
await pc.addIceCandidate(payload.candidate)
|
|
14428
|
+
respond(id, true, {})
|
|
14429
|
+
return
|
|
14430
|
+
}
|
|
14431
|
+
if (method === 'getStats') {
|
|
14432
|
+
respond(id, true, serializeStats(await pc.getStats()))
|
|
14433
|
+
return
|
|
14434
|
+
}
|
|
14435
|
+
if (method === 'channelSend') {
|
|
14436
|
+
const channel = channels.get(payload.channelId)
|
|
14437
|
+
if (channel === undefined) throw new Error('unknown data channel')
|
|
14438
|
+
channel.send(payload.text ?? Buffer.from(payload.base64, 'base64'))
|
|
14439
|
+
respond(id, true, {})
|
|
14440
|
+
return
|
|
14441
|
+
}
|
|
14442
|
+
if (method === 'channelClose') {
|
|
14443
|
+
channels.get(payload.channelId)?.close()
|
|
14444
|
+
respond(id, true, {})
|
|
14445
|
+
return
|
|
14446
|
+
}
|
|
14447
|
+
if (method === 'close') {
|
|
14448
|
+
for (const channel of channels.values()) {
|
|
14449
|
+
try { channel.close() } catch {}
|
|
14450
|
+
}
|
|
14451
|
+
channels.clear()
|
|
14452
|
+
try { pc?.close() } catch {}
|
|
14453
|
+
respond(id, true, {})
|
|
14454
|
+
setTimeout(() => process.exit(0), 0)
|
|
14455
|
+
return
|
|
14456
|
+
}
|
|
14457
|
+
throw new Error('unknown method ' + method)
|
|
14458
|
+
} catch (error) {
|
|
14459
|
+
respond(id, false, undefined, error)
|
|
14460
|
+
}
|
|
14461
|
+
}
|
|
14462
|
+
|
|
14463
|
+
const rl = createInterface({ input: process.stdin })
|
|
14464
|
+
rl.on('line', line => {
|
|
14465
|
+
chain = chain.then(() => handle(JSON.parse(line))).catch(error => {
|
|
14466
|
+
send({ id: -1, ok: false, error: String(error?.message ?? error) })
|
|
14467
|
+
})
|
|
14468
|
+
})
|
|
14469
|
+
process.stdin.on('end', () => {
|
|
14470
|
+
try { pc?.close() } catch {}
|
|
14471
|
+
process.exit(0)
|
|
14472
|
+
})
|
|
14473
|
+
`;
|
|
14474
|
+
|
|
14475
|
+
// src/werift-rtc.ts
|
|
13655
14476
|
var cachedFactory;
|
|
13656
|
-
|
|
13657
|
-
|
|
14477
|
+
var cachedWerift;
|
|
14478
|
+
var cachedNativeFactory;
|
|
14479
|
+
async function loadNodeRtcFactory(options = {}) {
|
|
14480
|
+
const nativeFactory = isElectronRuntime() ? await loadExternalNativeRtcFactory().catch(() => void 0) : await loadNativeRtcFactory().catch(() => void 0);
|
|
14481
|
+
return nativeFactory ?? await loadWeriftFactory(options);
|
|
14482
|
+
}
|
|
14483
|
+
async function loadWeriftFactory(options = {}) {
|
|
14484
|
+
const cacheable = isCacheableFactoryOptions(options);
|
|
14485
|
+
if (cacheable && cachedFactory !== void 0) return cachedFactory;
|
|
13658
14486
|
try {
|
|
13659
|
-
const werift = await import("werift");
|
|
13660
|
-
|
|
13661
|
-
|
|
14487
|
+
const werift = cachedWerift ?? await import("werift");
|
|
14488
|
+
cachedWerift = werift;
|
|
14489
|
+
const routeCandidates = options.preferredHostIpv4Candidates ?? await detectRouteHostIpv4Candidates(options.routeTargets ?? [], options.routeProbeTimeoutMs);
|
|
14490
|
+
const factory = buildWeriftFactory(werift, { ...options, preferredHostIpv4Candidates: routeCandidates });
|
|
14491
|
+
if (cacheable) cachedFactory = factory;
|
|
14492
|
+
return factory;
|
|
13662
14493
|
} catch {
|
|
13663
14494
|
return void 0;
|
|
13664
14495
|
}
|
|
13665
14496
|
}
|
|
13666
|
-
function
|
|
14497
|
+
async function loadNativeRtcFactory() {
|
|
14498
|
+
if (cachedNativeFactory !== void 0) return cachedNativeFactory;
|
|
14499
|
+
try {
|
|
14500
|
+
const wrtc = await import("@roamhq/wrtc");
|
|
14501
|
+
const RTCPeerConnection2 = wrtc.RTCPeerConnection ?? wrtc.default?.RTCPeerConnection;
|
|
14502
|
+
if (RTCPeerConnection2 === void 0) return void 0;
|
|
14503
|
+
cachedNativeFactory = buildNativeRtcFactory(RTCPeerConnection2);
|
|
14504
|
+
return cachedNativeFactory;
|
|
14505
|
+
} catch {
|
|
14506
|
+
return void 0;
|
|
14507
|
+
}
|
|
14508
|
+
}
|
|
14509
|
+
function isElectronRuntime() {
|
|
14510
|
+
const versions = process.versions;
|
|
14511
|
+
return typeof versions.electron === "string" || process.env.ELECTRON_RUN_AS_NODE === "1";
|
|
14512
|
+
}
|
|
14513
|
+
function buildNativeRtcFactory(RTCPeerConnection2) {
|
|
14514
|
+
return {
|
|
14515
|
+
create(configuration) {
|
|
14516
|
+
const raw = new RTCPeerConnection2({ iceServers: configuration.iceServers });
|
|
14517
|
+
return {
|
|
14518
|
+
get connectionState() {
|
|
14519
|
+
return raw.connectionState;
|
|
14520
|
+
},
|
|
14521
|
+
get iceConnectionState() {
|
|
14522
|
+
return raw.iceConnectionState;
|
|
14523
|
+
},
|
|
14524
|
+
get iceGatheringState() {
|
|
14525
|
+
return raw.iceGatheringState;
|
|
14526
|
+
},
|
|
14527
|
+
get signalingState() {
|
|
14528
|
+
return raw.signalingState;
|
|
14529
|
+
},
|
|
14530
|
+
set onconnectionstatechange(value) {
|
|
14531
|
+
raw.onconnectionstatechange = value;
|
|
14532
|
+
},
|
|
14533
|
+
get onconnectionstatechange() {
|
|
14534
|
+
return raw.onconnectionstatechange;
|
|
14535
|
+
},
|
|
14536
|
+
set oniceconnectionstatechange(value) {
|
|
14537
|
+
raw.oniceconnectionstatechange = value;
|
|
14538
|
+
},
|
|
14539
|
+
get oniceconnectionstatechange() {
|
|
14540
|
+
return raw.oniceconnectionstatechange;
|
|
14541
|
+
},
|
|
14542
|
+
set onicegatheringstatechange(value) {
|
|
14543
|
+
raw.onicegatheringstatechange = value;
|
|
14544
|
+
},
|
|
14545
|
+
get onicegatheringstatechange() {
|
|
14546
|
+
return raw.onicegatheringstatechange;
|
|
14547
|
+
},
|
|
14548
|
+
set onicecandidate(value) {
|
|
14549
|
+
raw.onicecandidate = value === null ? null : (event) => value({
|
|
14550
|
+
candidate: event.candidate === null ? null : normalizeNativeCandidate(event.candidate)
|
|
14551
|
+
});
|
|
14552
|
+
},
|
|
14553
|
+
get onicecandidate() {
|
|
14554
|
+
return raw.onicecandidate;
|
|
14555
|
+
},
|
|
14556
|
+
set ondatachannel(value) {
|
|
14557
|
+
raw.ondatachannel = value === null ? null : (event) => value({ channel: adaptNativeDataChannel(event.channel) });
|
|
14558
|
+
},
|
|
14559
|
+
get ondatachannel() {
|
|
14560
|
+
return raw.ondatachannel;
|
|
14561
|
+
},
|
|
14562
|
+
createDataChannel(label, options) {
|
|
14563
|
+
return adaptNativeDataChannel(raw.createDataChannel(label, options));
|
|
14564
|
+
},
|
|
14565
|
+
createOffer() {
|
|
14566
|
+
return raw.createOffer();
|
|
14567
|
+
},
|
|
14568
|
+
createAnswer() {
|
|
14569
|
+
return raw.createAnswer();
|
|
14570
|
+
},
|
|
14571
|
+
setLocalDescription(description) {
|
|
14572
|
+
return raw.setLocalDescription(description);
|
|
14573
|
+
},
|
|
14574
|
+
setRemoteDescription(description) {
|
|
14575
|
+
return raw.setRemoteDescription(description);
|
|
14576
|
+
},
|
|
14577
|
+
addIceCandidate(candidate) {
|
|
14578
|
+
return raw.addIceCandidate(candidate);
|
|
14579
|
+
},
|
|
14580
|
+
getStats() {
|
|
14581
|
+
return raw.getStats();
|
|
14582
|
+
},
|
|
14583
|
+
close() {
|
|
14584
|
+
raw.close();
|
|
14585
|
+
}
|
|
14586
|
+
};
|
|
14587
|
+
}
|
|
14588
|
+
};
|
|
14589
|
+
}
|
|
14590
|
+
function buildWeriftFactory(werift, options = {}) {
|
|
13667
14591
|
return {
|
|
13668
14592
|
create(configuration) {
|
|
13669
|
-
const
|
|
14593
|
+
const hostIpv4Candidates = detectHostIpv4Candidates(
|
|
14594
|
+
options.interfaces ?? networkInterfaces(),
|
|
14595
|
+
options.preferredHostIpv4Candidates
|
|
14596
|
+
);
|
|
14597
|
+
const allowedHostIpv4 = new Set(hostIpv4Candidates);
|
|
13670
14598
|
const raw = new werift.RTCPeerConnection({
|
|
13671
|
-
iceServers: configuration.iceServers ?? [],
|
|
14599
|
+
iceServers: orderIceServersForWerift(configuration.iceServers ?? []),
|
|
14600
|
+
iceUseIpv4: true,
|
|
13672
14601
|
iceUseIpv6: false,
|
|
13673
14602
|
iceUseLinkLocalAddress: false,
|
|
13674
|
-
...
|
|
14603
|
+
...hostIpv4Candidates.length === 0 ? {} : {
|
|
14604
|
+
iceAdditionalHostAddresses: hostIpv4Candidates,
|
|
14605
|
+
iceFilterCandidatePair: (pair) => {
|
|
14606
|
+
const allowed = shouldUseCandidatePair(pair, allowedHostIpv4);
|
|
14607
|
+
if (!allowed) {
|
|
14608
|
+
configuration.onDiagnostic?.({
|
|
14609
|
+
type: "candidate-pair-filtered",
|
|
14610
|
+
localCandidate: summarizeWeriftCandidate(localCandidate(pair)),
|
|
14611
|
+
reason: "local-host-not-allowed"
|
|
14612
|
+
});
|
|
14613
|
+
}
|
|
14614
|
+
return allowed;
|
|
14615
|
+
}
|
|
14616
|
+
}
|
|
13675
14617
|
});
|
|
13676
14618
|
let onIceCandidate = null;
|
|
13677
14619
|
let onDataChannel = null;
|
|
13678
14620
|
raw.onicecandidate = (event) => {
|
|
13679
14621
|
if (onIceCandidate === null) return;
|
|
13680
|
-
|
|
14622
|
+
const candidate = event.candidate === void 0 ? null : event.candidate.toJSON();
|
|
14623
|
+
if (candidate !== null && !shouldAdvertiseCandidate(candidate, allowedHostIpv4)) {
|
|
14624
|
+
configuration.onDiagnostic?.({
|
|
14625
|
+
type: "local-candidate-filtered",
|
|
14626
|
+
candidate: summarizeIceCandidate(candidate),
|
|
14627
|
+
reason: "local-host-not-allowed"
|
|
14628
|
+
});
|
|
14629
|
+
return;
|
|
14630
|
+
}
|
|
14631
|
+
onIceCandidate({ candidate });
|
|
13681
14632
|
};
|
|
13682
14633
|
raw.ondatachannel = (event) => {
|
|
13683
14634
|
if (onDataChannel === null) return;
|
|
@@ -13726,8 +14677,8 @@ function buildWeriftFactory(werift) {
|
|
|
13726
14677
|
get ondatachannel() {
|
|
13727
14678
|
return onDataChannel;
|
|
13728
14679
|
},
|
|
13729
|
-
createDataChannel(label,
|
|
13730
|
-
return adaptDataChannel2(raw.createDataChannel(label, { ordered:
|
|
14680
|
+
createDataChannel(label, options2) {
|
|
14681
|
+
return adaptDataChannel2(raw.createDataChannel(label, { ordered: options2?.ordered ?? true }));
|
|
13731
14682
|
},
|
|
13732
14683
|
createOffer() {
|
|
13733
14684
|
return raw.createOffer();
|
|
@@ -13809,27 +14760,245 @@ function adaptDataChannel2(raw) {
|
|
|
13809
14760
|
}
|
|
13810
14761
|
};
|
|
13811
14762
|
}
|
|
14763
|
+
function adaptNativeDataChannel(raw) {
|
|
14764
|
+
let onmessage = null;
|
|
14765
|
+
return {
|
|
14766
|
+
get label() {
|
|
14767
|
+
return raw.label;
|
|
14768
|
+
},
|
|
14769
|
+
get ordered() {
|
|
14770
|
+
return raw.ordered;
|
|
14771
|
+
},
|
|
14772
|
+
get readyState() {
|
|
14773
|
+
return raw.readyState;
|
|
14774
|
+
},
|
|
14775
|
+
get bufferedAmount() {
|
|
14776
|
+
return raw.bufferedAmount;
|
|
14777
|
+
},
|
|
14778
|
+
get binaryType() {
|
|
14779
|
+
return raw.binaryType;
|
|
14780
|
+
},
|
|
14781
|
+
set binaryType(value) {
|
|
14782
|
+
raw.binaryType = value;
|
|
14783
|
+
},
|
|
14784
|
+
set onopen(value) {
|
|
14785
|
+
raw.onopen = value;
|
|
14786
|
+
},
|
|
14787
|
+
get onopen() {
|
|
14788
|
+
return raw.onopen;
|
|
14789
|
+
},
|
|
14790
|
+
set onmessage(value) {
|
|
14791
|
+
onmessage = value;
|
|
14792
|
+
raw.onmessage = value === null ? null : (event) => value({ data: normalizeNativeMessageData(event.data) });
|
|
14793
|
+
},
|
|
14794
|
+
get onmessage() {
|
|
14795
|
+
return onmessage;
|
|
14796
|
+
},
|
|
14797
|
+
set onclose(value) {
|
|
14798
|
+
raw.onclose = value;
|
|
14799
|
+
},
|
|
14800
|
+
get onclose() {
|
|
14801
|
+
return raw.onclose;
|
|
14802
|
+
},
|
|
14803
|
+
set onerror(value) {
|
|
14804
|
+
raw.onerror = value;
|
|
14805
|
+
},
|
|
14806
|
+
get onerror() {
|
|
14807
|
+
return raw.onerror;
|
|
14808
|
+
},
|
|
14809
|
+
set onbufferedamountlow(value) {
|
|
14810
|
+
raw.onbufferedamountlow = value;
|
|
14811
|
+
},
|
|
14812
|
+
get onbufferedamountlow() {
|
|
14813
|
+
return raw.onbufferedamountlow;
|
|
14814
|
+
},
|
|
14815
|
+
send(data) {
|
|
14816
|
+
raw.send(typeof data === "string" ? data : Buffer.from(data));
|
|
14817
|
+
},
|
|
14818
|
+
close() {
|
|
14819
|
+
raw.close();
|
|
14820
|
+
}
|
|
14821
|
+
};
|
|
14822
|
+
}
|
|
13812
14823
|
function toArrayBuffer2(data) {
|
|
13813
14824
|
if (typeof data === "string") return data;
|
|
13814
14825
|
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
13815
14826
|
}
|
|
13816
|
-
function
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
14827
|
+
function normalizeNativeCandidate(candidate) {
|
|
14828
|
+
if (typeof candidate.toJSON === "function") return candidate.toJSON();
|
|
14829
|
+
return {
|
|
14830
|
+
candidate: candidate.candidate,
|
|
14831
|
+
sdpMid: candidate.sdpMid,
|
|
14832
|
+
sdpMLineIndex: candidate.sdpMLineIndex,
|
|
14833
|
+
usernameFragment: candidate.usernameFragment
|
|
14834
|
+
};
|
|
14835
|
+
}
|
|
14836
|
+
function normalizeNativeMessageData(data) {
|
|
14837
|
+
if (typeof data === "string") return data;
|
|
14838
|
+
if (data instanceof ArrayBuffer) return data;
|
|
14839
|
+
if (ArrayBuffer.isView(data)) {
|
|
14840
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
14841
|
+
}
|
|
14842
|
+
return new Uint8Array().buffer;
|
|
14843
|
+
}
|
|
14844
|
+
function detectHostIpv4Candidates(interfaces, preferredHostIpv4Candidates = []) {
|
|
14845
|
+
const candidates = [];
|
|
14846
|
+
const routePreference = new Map(preferredHostIpv4Candidates.map((ip, index) => [ip, 1e4 - index]));
|
|
14847
|
+
for (const [name2, addresses] of Object.entries(interfaces)) {
|
|
14848
|
+
const interfaceScore = physicalInterfaceScore(name2);
|
|
14849
|
+
if (interfaceScore === void 0) continue;
|
|
13821
14850
|
for (const address of addresses ?? []) {
|
|
13822
|
-
if (address.internal || address.family
|
|
14851
|
+
if (address.internal || !isIpv4Family(address.family)) continue;
|
|
13823
14852
|
const ip = address.address;
|
|
13824
|
-
if (
|
|
13825
|
-
|
|
13826
|
-
|
|
14853
|
+
if (!isUsableIpv4(ip)) continue;
|
|
14854
|
+
candidates.push({
|
|
14855
|
+
name: name2,
|
|
14856
|
+
ip,
|
|
14857
|
+
score: interfaceScore + (isPrivate(ip) ? 1e3 : 0) + (routePreference.get(ip) ?? 0)
|
|
14858
|
+
});
|
|
13827
14859
|
}
|
|
13828
14860
|
}
|
|
13829
|
-
|
|
14861
|
+
candidates.sort((left, right) => {
|
|
14862
|
+
const score = right.score - left.score;
|
|
14863
|
+
if (score !== 0) return score;
|
|
14864
|
+
const name2 = left.name.localeCompare(right.name, "en");
|
|
14865
|
+
return name2 === 0 ? left.ip.localeCompare(right.ip, "en", { numeric: true }) : name2;
|
|
14866
|
+
});
|
|
14867
|
+
return [...new Set(candidates.map((candidate) => candidate.ip))];
|
|
14868
|
+
}
|
|
14869
|
+
async function detectRouteHostIpv4Candidates(targets, timeoutMs = 500) {
|
|
14870
|
+
const detected = await Promise.all(targets.map(async (target) => {
|
|
14871
|
+
const routeTarget = parseRouteTarget(target);
|
|
14872
|
+
return routeTarget === void 0 ? void 0 : await detectRouteHostIpv4(routeTarget, timeoutMs).catch(() => void 0);
|
|
14873
|
+
}));
|
|
14874
|
+
return [...new Set(detected.filter((ip) => ip !== void 0 && isUsableIpv4(ip)))];
|
|
14875
|
+
}
|
|
14876
|
+
function shouldUseCandidatePair(pair, allowedHostIpv4) {
|
|
14877
|
+
if (allowedHostIpv4.size === 0) return true;
|
|
14878
|
+
const candidate = localCandidate(pair);
|
|
14879
|
+
if (candidate === void 0) return true;
|
|
14880
|
+
return shouldUseLocalCandidate(candidate, allowedHostIpv4);
|
|
14881
|
+
}
|
|
14882
|
+
function shouldAdvertiseCandidate(candidate, allowedHostIpv4) {
|
|
14883
|
+
if (allowedHostIpv4.size === 0) return true;
|
|
14884
|
+
const parsed = parseIceCandidate(candidate);
|
|
14885
|
+
if (parsed === void 0) return true;
|
|
14886
|
+
return shouldUseLocalCandidate(parsed, allowedHostIpv4);
|
|
14887
|
+
}
|
|
14888
|
+
function shouldUseLocalCandidate(candidate, allowedHostIpv4) {
|
|
14889
|
+
if (candidate.type === "host") return candidate.host !== void 0 && allowedHostIpv4.has(candidate.host);
|
|
14890
|
+
if (candidate.type === "srflx" && candidate.relatedAddress !== void 0) {
|
|
14891
|
+
return allowedHostIpv4.has(candidate.relatedAddress);
|
|
14892
|
+
}
|
|
14893
|
+
return true;
|
|
14894
|
+
}
|
|
14895
|
+
function localCandidate(pair) {
|
|
14896
|
+
try {
|
|
14897
|
+
return pair.localCandidate;
|
|
14898
|
+
} catch {
|
|
14899
|
+
return void 0;
|
|
14900
|
+
}
|
|
14901
|
+
}
|
|
14902
|
+
function summarizeWeriftCandidate(candidate) {
|
|
14903
|
+
if (candidate === void 0) return void 0;
|
|
14904
|
+
const address = summarizeAddress(candidate.host);
|
|
14905
|
+
const related = summarizeAddress(candidate.relatedAddress);
|
|
14906
|
+
return {
|
|
14907
|
+
candidateType: candidate.type === "host" || candidate.type === "srflx" || candidate.type === "prflx" || candidate.type === "relay" ? candidate.type : "unknown",
|
|
14908
|
+
...address,
|
|
14909
|
+
...candidate.relatedAddress === void 0 ? {} : { relatedAddressFamily: related.addressFamily, relatedAddressScope: related.addressScope }
|
|
14910
|
+
};
|
|
14911
|
+
}
|
|
14912
|
+
function orderIceServersForWerift(iceServers) {
|
|
14913
|
+
return iceServers.map((server) => {
|
|
14914
|
+
const urls = Array.isArray(server.urls) ? [...server.urls] : [server.urls];
|
|
14915
|
+
urls.sort((left, right) => iceUrlScore(left) - iceUrlScore(right));
|
|
14916
|
+
return { ...server, urls: Array.isArray(server.urls) ? urls : urls[0] ?? server.urls };
|
|
14917
|
+
});
|
|
14918
|
+
}
|
|
14919
|
+
function iceUrlScore(url) {
|
|
14920
|
+
const value = url.trim().toLowerCase();
|
|
14921
|
+
if (value.startsWith("stun:") || value.startsWith("stuns:")) return 0;
|
|
14922
|
+
if (value.startsWith("turn:") && value.includes("transport=tcp")) return 10;
|
|
14923
|
+
if (value.startsWith("turns:") && value.includes("transport=tcp")) return 20;
|
|
14924
|
+
if (value.startsWith("turn:") && value.includes("transport=udp")) return 30;
|
|
14925
|
+
if (value.startsWith("turns:")) return 40;
|
|
14926
|
+
if (value.startsWith("turn:")) return 50;
|
|
14927
|
+
return 100;
|
|
14928
|
+
}
|
|
14929
|
+
function parseRouteTarget(value) {
|
|
14930
|
+
try {
|
|
14931
|
+
const url = new URL(value);
|
|
14932
|
+
const port = Number(url.port || (url.protocol === "http:" ? "80" : "443"));
|
|
14933
|
+
if (!Number.isSafeInteger(port) || port <= 0 || port > 65535 || url.hostname.length === 0) return void 0;
|
|
14934
|
+
return { host: url.hostname, port };
|
|
14935
|
+
} catch {
|
|
14936
|
+
return void 0;
|
|
14937
|
+
}
|
|
13830
14938
|
}
|
|
13831
|
-
function
|
|
13832
|
-
|
|
14939
|
+
async function detectRouteHostIpv4(target, timeoutMs) {
|
|
14940
|
+
const socket = createSocket("udp4");
|
|
14941
|
+
try {
|
|
14942
|
+
return await new Promise((resolve2, reject) => {
|
|
14943
|
+
const timer = setTimeout(() => {
|
|
14944
|
+
cleanup();
|
|
14945
|
+
resolve2(void 0);
|
|
14946
|
+
}, timeoutMs);
|
|
14947
|
+
const cleanup = () => {
|
|
14948
|
+
clearTimeout(timer);
|
|
14949
|
+
socket.off("error", onError);
|
|
14950
|
+
};
|
|
14951
|
+
const onError = (error) => {
|
|
14952
|
+
cleanup();
|
|
14953
|
+
reject(error);
|
|
14954
|
+
};
|
|
14955
|
+
socket.once("error", onError);
|
|
14956
|
+
socket.connect(target.port, target.host, () => {
|
|
14957
|
+
cleanup();
|
|
14958
|
+
const address = socket.address();
|
|
14959
|
+
resolve2(typeof address === "string" ? void 0 : address.address);
|
|
14960
|
+
});
|
|
14961
|
+
});
|
|
14962
|
+
} finally {
|
|
14963
|
+
socket.close();
|
|
14964
|
+
}
|
|
14965
|
+
}
|
|
14966
|
+
function isCacheableFactoryOptions(options) {
|
|
14967
|
+
return options.interfaces === void 0 && options.preferredHostIpv4Candidates === void 0 && options.routeTargets === void 0;
|
|
14968
|
+
}
|
|
14969
|
+
function parseIceCandidate(candidate) {
|
|
14970
|
+
const value = candidate.candidate?.trim();
|
|
14971
|
+
if (value === void 0 || value.length === 0) return void 0;
|
|
14972
|
+
const parts = value.replace(/^candidate:/, "").split(/\s+/);
|
|
14973
|
+
const typeIndex = parts.indexOf("typ");
|
|
14974
|
+
if (typeIndex < 0) return void 0;
|
|
14975
|
+
const host = parts[4];
|
|
14976
|
+
const type = parts[typeIndex + 1];
|
|
14977
|
+
const relatedAddressIndex = parts.indexOf("raddr");
|
|
14978
|
+
return {
|
|
14979
|
+
...host === void 0 ? {} : { host },
|
|
14980
|
+
...type === void 0 ? {} : { type },
|
|
14981
|
+
...relatedAddressIndex < 0 || parts[relatedAddressIndex + 1] === void 0 ? {} : { relatedAddress: parts[relatedAddressIndex + 1] }
|
|
14982
|
+
};
|
|
14983
|
+
}
|
|
14984
|
+
function physicalInterfaceScore(name2) {
|
|
14985
|
+
const value = name2.toLowerCase();
|
|
14986
|
+
if (/^(utun|tun|tap|ppp|bridge|awdl|llw|gif|stf|anpi|ap\d|vmnet|veth|docker|br-|vboxnet|lo)/.test(value)) {
|
|
14987
|
+
return void 0;
|
|
14988
|
+
}
|
|
14989
|
+
if (/^(eth|eno|ens|enp)/.test(value)) return 500;
|
|
14990
|
+
if (/^(en|wlan|wl|wifi|wi-fi)/.test(value)) return 450;
|
|
14991
|
+
return 300;
|
|
14992
|
+
}
|
|
14993
|
+
function isIpv4Family(family) {
|
|
14994
|
+
return family === "IPv4" || family === 4;
|
|
14995
|
+
}
|
|
14996
|
+
function isUsableIpv4(ip) {
|
|
14997
|
+
const parts = ip.split(".").map(Number);
|
|
14998
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
14999
|
+
if (parts[0] === 0 || parts[0] === 127 || parts[0] >= 224) return false;
|
|
15000
|
+
if (parts[0] === 169 && parts[1] === 254) return false;
|
|
15001
|
+
return !isCgnat(ip);
|
|
13833
15002
|
}
|
|
13834
15003
|
function isCgnat(ip) {
|
|
13835
15004
|
const parts = ip.split(".").map(Number);
|
|
@@ -13846,8 +15015,9 @@ function isPrivate(ip) {
|
|
|
13846
15015
|
// src/client-runtime.ts
|
|
13847
15016
|
var REMOTE_COMMAND_LIST_MIN_VERSION = [0, 3, 16];
|
|
13848
15017
|
var REMOTE_FILE_VIEWER_MIN_VERSION = [0, 3, 17];
|
|
15018
|
+
var DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 12e3;
|
|
13849
15019
|
var ClientModeRuntime = class {
|
|
13850
|
-
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider =
|
|
15020
|
+
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory) {
|
|
13851
15021
|
this.config = config;
|
|
13852
15022
|
this.identities = identities;
|
|
13853
15023
|
this.server = server;
|
|
@@ -14083,61 +15253,76 @@ var ClientModeRuntime = class {
|
|
|
14083
15253
|
const presence = await this.server.presenceFor(targetDeviceId);
|
|
14084
15254
|
if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
|
|
14085
15255
|
const credentials = await this.server.authenticate(identity);
|
|
14086
|
-
const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider().catch(() => void 0);
|
|
15256
|
+
const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => void 0);
|
|
14087
15257
|
let webRtcFallback = false;
|
|
14088
|
-
const createTransport = (
|
|
15258
|
+
const createTransport = (attempt) => new AdaptiveTransport(
|
|
14089
15259
|
websocketUrl(this.server.baseUrl),
|
|
14090
15260
|
{
|
|
14091
15261
|
role: "client",
|
|
14092
15262
|
deviceId: identity.deviceId,
|
|
14093
15263
|
accessToken: credentials.accessToken,
|
|
14094
15264
|
targetDeviceId,
|
|
14095
|
-
forceRelay: this.config.forceRelay ||
|
|
14096
|
-
preferredTransports:
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
15265
|
+
forceRelay: this.config.forceRelay || attempt === "relay",
|
|
15266
|
+
preferredTransports: preferredTransportsForAttempt(attempt),
|
|
15267
|
+
negotiateTimeoutMs: attempt === "direct" ? DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS : void 0,
|
|
15268
|
+
...rtcFactory === void 0 || attempt === "relay" ? {} : { rtcFactory },
|
|
15269
|
+
fetchIceServers: async (connectionId) => iceServersForAttempt(attempt, await this.server.turnCredentials(connectionId)),
|
|
15270
|
+
onWebRtcFallback: (error, diagnostics) => {
|
|
14100
15271
|
webRtcFallback = true;
|
|
14101
|
-
this.logger.warn("remote Harness WebRTC failed; using relay", {
|
|
15272
|
+
this.logger.warn(attempt === "direct" ? "remote Harness direct WebRTC failed; trying TURN" : "remote Harness TURN WebRTC failed; using relay", {
|
|
14102
15273
|
targetDeviceId: shortId(target.deviceId),
|
|
14103
|
-
|
|
15274
|
+
attempt,
|
|
15275
|
+
reason: diagnosticReason(error),
|
|
15276
|
+
...webrtcDiagnosticsLogFields(diagnostics)
|
|
14104
15277
|
});
|
|
14105
15278
|
}
|
|
14106
15279
|
}
|
|
14107
15280
|
);
|
|
14108
|
-
|
|
14109
|
-
let
|
|
15281
|
+
const attempts = this.config.forceRelay || rtcFactory === void 0 ? ["relay"] : ["direct", "turn", "relay"];
|
|
15282
|
+
let transport;
|
|
15283
|
+
let client;
|
|
14110
15284
|
try {
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
await client.close();
|
|
14115
|
-
transport = createTransport(true);
|
|
15285
|
+
for (const attempt of attempts) {
|
|
15286
|
+
webRtcFallback = false;
|
|
15287
|
+
transport = createTransport(attempt);
|
|
14116
15288
|
client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 6e4);
|
|
14117
15289
|
await client.connect();
|
|
14118
15290
|
signal?.throwIfAborted();
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
15291
|
+
if (attempt === "relay" || !webRtcFallback) break;
|
|
15292
|
+
await client.close();
|
|
15293
|
+
client = void 0;
|
|
15294
|
+
transport = void 0;
|
|
15295
|
+
if (attempt === "turn") {
|
|
15296
|
+
this.logger.info("remote Harness relay fallback re-established", {
|
|
15297
|
+
targetDeviceId: shortId(target.deviceId)
|
|
15298
|
+
});
|
|
15299
|
+
}
|
|
14122
15300
|
}
|
|
14123
|
-
client
|
|
14124
|
-
|
|
15301
|
+
if (client === void 0 || transport === void 0) {
|
|
15302
|
+
throw new ClientModeError("CONNECTION_FAILED", "Unable to establish a remote transport.", true);
|
|
15303
|
+
}
|
|
15304
|
+
const connectedClient = client;
|
|
15305
|
+
const connectedTransport = transport;
|
|
15306
|
+
connectedClient.onClose(() => {
|
|
15307
|
+
if (this.connected?.client !== connectedClient) return;
|
|
14125
15308
|
this.connected = void 0;
|
|
14126
15309
|
this.pendingWorkspaceSelection = void 0;
|
|
14127
15310
|
this.proxySwitch.selectLocal();
|
|
14128
15311
|
this.gatewaySwitch.selectLocal();
|
|
14129
|
-
void
|
|
15312
|
+
void connectedClient.close().catch(() => void 0);
|
|
14130
15313
|
this.logger.warn("remote Harness transport closed; falling back to local mode", {
|
|
14131
15314
|
targetDeviceId: shortId(target.deviceId)
|
|
14132
15315
|
});
|
|
14133
15316
|
});
|
|
15317
|
+
const connectionDetails = await connectedTransport.connectionDetails().catch(() => void 0);
|
|
14134
15318
|
this.logger.info("remote Harness transport ready", {
|
|
14135
15319
|
targetDeviceId: shortId(target.deviceId),
|
|
14136
|
-
transport:
|
|
15320
|
+
transport: connectedClient.getStats().mode,
|
|
15321
|
+
...webrtcDiagnosticsLogFields(connectionDetails?.webRtc?.diagnostics)
|
|
14137
15322
|
});
|
|
14138
|
-
return { client, target, transport, features: remoteHostFeatures(serverDevice.clientVersion) };
|
|
15323
|
+
return { client: connectedClient, target, transport: connectedTransport, features: remoteHostFeatures(serverDevice.clientVersion) };
|
|
14139
15324
|
} catch (error) {
|
|
14140
|
-
await client
|
|
15325
|
+
await client?.close().catch(() => void 0);
|
|
14141
15326
|
throw error;
|
|
14142
15327
|
}
|
|
14143
15328
|
}
|
|
@@ -14284,6 +15469,20 @@ function websocketUrl(baseUrl) {
|
|
|
14284
15469
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/ws/v1/connect`;
|
|
14285
15470
|
return url.toString();
|
|
14286
15471
|
}
|
|
15472
|
+
function webrtcDiagnosticsLogFields(diagnostics) {
|
|
15473
|
+
if (diagnostics === void 0) return {};
|
|
15474
|
+
return {
|
|
15475
|
+
rtcConnectionState: diagnostics.connectionState,
|
|
15476
|
+
rtcIceConnectionState: diagnostics.iceConnectionState,
|
|
15477
|
+
rtcIceGatheringState: diagnostics.iceGatheringState,
|
|
15478
|
+
rtcLocalCandidates: diagnostics.localCandidates,
|
|
15479
|
+
rtcRemoteCandidates: diagnostics.remoteCandidates,
|
|
15480
|
+
rtcCandidatePairs: diagnostics.candidatePairs,
|
|
15481
|
+
rtcFilteredLocalCandidates: diagnostics.filteredLocalCandidates,
|
|
15482
|
+
rtcFilteredCandidatePairs: diagnostics.filteredCandidatePairs,
|
|
15483
|
+
...diagnostics.selectedPath === void 0 ? {} : { rtcSelectedPath: diagnostics.selectedPath }
|
|
15484
|
+
};
|
|
15485
|
+
}
|
|
14287
15486
|
function record(value) {
|
|
14288
15487
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
14289
15488
|
throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
|
|
@@ -14361,6 +15560,14 @@ function diagnosticReason(error) {
|
|
|
14361
15560
|
const message = error.message.replace(/[\r\n\t]+/g, " ").slice(0, 240);
|
|
14362
15561
|
return code === void 0 ? message : `${code}: ${message}`;
|
|
14363
15562
|
}
|
|
15563
|
+
function preferredTransportsForAttempt(attempt) {
|
|
15564
|
+
if (attempt === "direct") return ["lan", "p2p", "relay"];
|
|
15565
|
+
if (attempt === "turn") return ["turn", "relay"];
|
|
15566
|
+
return ["relay"];
|
|
15567
|
+
}
|
|
15568
|
+
function iceServersForAttempt(attempt, iceServers) {
|
|
15569
|
+
return attempt === "direct" ? stunOnlyIceServers(iceServers) : iceServers;
|
|
15570
|
+
}
|
|
14364
15571
|
|
|
14365
15572
|
// src/control-runtime.ts
|
|
14366
15573
|
import { hostname as hostname2 } from "node:os";
|
|
@@ -14369,7 +15576,7 @@ import { hostname as hostname2 } from "node:os";
|
|
|
14369
15576
|
import { createHash } from "node:crypto";
|
|
14370
15577
|
import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
14371
15578
|
import { homedir } from "node:os";
|
|
14372
|
-
import { dirname, join } from "node:path";
|
|
15579
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
14373
15580
|
var identitySchema = external_exports.object({
|
|
14374
15581
|
schemaVersion: external_exports.literal(1),
|
|
14375
15582
|
deviceId: external_exports.string().uuid(),
|
|
@@ -14398,14 +15605,14 @@ var IdentityStore = class {
|
|
|
14398
15605
|
peers = /* @__PURE__ */ new Map();
|
|
14399
15606
|
constructor(options = {}) {
|
|
14400
15607
|
const env = options.env ?? process.env;
|
|
14401
|
-
const dshHome = env.DSH_HOME ||
|
|
14402
|
-
this.directory = options.directory ??
|
|
15608
|
+
const dshHome = env.DSH_HOME || join2(options.homeDirectory ?? homedir(), ".dsh");
|
|
15609
|
+
this.directory = options.directory ?? join2(dshHome, "remote");
|
|
14403
15610
|
}
|
|
14404
15611
|
async loadOrCreate(deviceName) {
|
|
14405
15612
|
await mkdir(this.directory, { recursive: true, mode: 448 });
|
|
14406
15613
|
await chmod(this.directory, 448);
|
|
14407
|
-
const devicePath =
|
|
14408
|
-
const keyPath =
|
|
15614
|
+
const devicePath = join2(this.directory, "device.json");
|
|
15615
|
+
const keyPath = join2(this.directory, "device.key");
|
|
14409
15616
|
const [hasDevice, hasKey] = await Promise.all([exists(devicePath), exists(keyPath)]);
|
|
14410
15617
|
if (hasDevice !== hasKey) {
|
|
14411
15618
|
throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
|
|
@@ -14474,7 +15681,7 @@ var IdentityStore = class {
|
|
|
14474
15681
|
return removed;
|
|
14475
15682
|
}
|
|
14476
15683
|
async loadPeers() {
|
|
14477
|
-
const path =
|
|
15684
|
+
const path = join2(this.directory, "trusted-peers.json");
|
|
14478
15685
|
if (!await exists(path)) {
|
|
14479
15686
|
await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
|
|
14480
15687
|
}
|
|
@@ -14490,7 +15697,7 @@ var IdentityStore = class {
|
|
|
14490
15697
|
this.peers = peers;
|
|
14491
15698
|
}
|
|
14492
15699
|
async savePeers() {
|
|
14493
|
-
await atomicJsonWrite(
|
|
15700
|
+
await atomicJsonWrite(join2(this.directory, "trusted-peers.json"), {
|
|
14494
15701
|
schemaVersion: 1,
|
|
14495
15702
|
peers: [...this.peers.values()]
|
|
14496
15703
|
}, 384);
|
|
@@ -14499,7 +15706,7 @@ var IdentityStore = class {
|
|
|
14499
15706
|
function serverStorageDirectory(root, serverUrl, role) {
|
|
14500
15707
|
const origin = new URL(serverUrl).origin;
|
|
14501
15708
|
const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
|
|
14502
|
-
return
|
|
15709
|
+
return join2(root, "servers", scope, role);
|
|
14503
15710
|
}
|
|
14504
15711
|
function fingerprint(publicKey) {
|
|
14505
15712
|
const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
|
|
@@ -14517,7 +15724,7 @@ async function atomicJsonWrite(path, value, mode) {
|
|
|
14517
15724
|
`, mode);
|
|
14518
15725
|
}
|
|
14519
15726
|
async function atomicTextWrite(path, value, mode) {
|
|
14520
|
-
await mkdir(
|
|
15727
|
+
await mkdir(dirname2(path), { recursive: true, mode: 448 });
|
|
14521
15728
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
14522
15729
|
await writeFile(temporary, value, { encoding: "utf8", mode, flag: "wx" });
|
|
14523
15730
|
await chmod(temporary, mode);
|
|
@@ -14542,7 +15749,7 @@ function safeErrorMessage(error) {
|
|
|
14542
15749
|
|
|
14543
15750
|
// src/server-credentials.ts
|
|
14544
15751
|
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
14545
|
-
import { dirname as
|
|
15752
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
14546
15753
|
var credentialSchema = external_exports.object({
|
|
14547
15754
|
schemaVersion: external_exports.literal(1),
|
|
14548
15755
|
serverUrl: external_exports.string().url(),
|
|
@@ -14557,7 +15764,7 @@ var credentialSchema = external_exports.object({
|
|
|
14557
15764
|
var ServerCredentialStore = class {
|
|
14558
15765
|
path;
|
|
14559
15766
|
constructor(directory) {
|
|
14560
|
-
this.path =
|
|
15767
|
+
this.path = join3(directory, "server-credentials.json");
|
|
14561
15768
|
}
|
|
14562
15769
|
async load(serverUrl, deviceId) {
|
|
14563
15770
|
if (!await exists2(this.path)) return void 0;
|
|
@@ -14584,7 +15791,7 @@ var ServerCredentialsInvalidError = class extends Error {
|
|
|
14584
15791
|
code = "SERVER_CREDENTIALS_INVALID";
|
|
14585
15792
|
};
|
|
14586
15793
|
async function atomicWrite(path, contents) {
|
|
14587
|
-
await mkdir2(
|
|
15794
|
+
await mkdir2(dirname3(path), { recursive: true, mode: 448 });
|
|
14588
15795
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
14589
15796
|
await writeFile2(temporary, contents, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
14590
15797
|
await chmod2(temporary, 384);
|
|
@@ -15044,7 +16251,7 @@ function shortId2(value) {
|
|
|
15044
16251
|
|
|
15045
16252
|
// src/harness-version.ts
|
|
15046
16253
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
15047
|
-
import { dirname as
|
|
16254
|
+
import { dirname as dirname4, isAbsolute, join as join4 } from "node:path";
|
|
15048
16255
|
var LEGACY_PLACEHOLDER_VERSION = "0.0.1";
|
|
15049
16256
|
function normalizeHarnessVersion(value) {
|
|
15050
16257
|
if (typeof value !== "string") return void 0;
|
|
@@ -15058,20 +16265,35 @@ function selectHarnessVersion(reportedVersion, distributionVersion) {
|
|
|
15058
16265
|
}
|
|
15059
16266
|
async function readHarnessDistributionVersion(entrypoint = process.argv[1]) {
|
|
15060
16267
|
if (entrypoint === void 0 || !isAbsolute(entrypoint)) return void 0;
|
|
15061
|
-
let directory =
|
|
16268
|
+
let directory = dirname4(entrypoint);
|
|
15062
16269
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
15063
16270
|
try {
|
|
15064
|
-
const manifest = JSON.parse(await readFile3(
|
|
16271
|
+
const manifest = JSON.parse(await readFile3(join4(directory, "package.json"), "utf8"));
|
|
15065
16272
|
if (manifest.name === "@deepseek-ai/dsh") return normalizeHarnessVersion(manifest.version);
|
|
15066
16273
|
} catch {
|
|
15067
16274
|
}
|
|
15068
|
-
const parent =
|
|
16275
|
+
const parent = dirname4(directory);
|
|
15069
16276
|
if (parent === directory) break;
|
|
15070
16277
|
directory = parent;
|
|
15071
16278
|
}
|
|
15072
16279
|
return void 0;
|
|
15073
16280
|
}
|
|
15074
16281
|
|
|
16282
|
+
// src/safe-error.ts
|
|
16283
|
+
var RpcError = class extends Error {
|
|
16284
|
+
constructor(code, message, details, retryable = false) {
|
|
16285
|
+
super(message);
|
|
16286
|
+
this.code = code;
|
|
16287
|
+
this.details = details;
|
|
16288
|
+
this.retryable = retryable;
|
|
16289
|
+
}
|
|
16290
|
+
};
|
|
16291
|
+
function safeErrorCode(error) {
|
|
16292
|
+
if (error instanceof RpcError) return error.code;
|
|
16293
|
+
if (error instanceof external_exports.ZodError) return "INVALID_MESSAGE";
|
|
16294
|
+
return "INTERNAL_ERROR";
|
|
16295
|
+
}
|
|
16296
|
+
|
|
15075
16297
|
// src/rpc-router.ts
|
|
15076
16298
|
var wireRequestSchema = external_exports.object({ method: external_exports.string().min(1), params: external_exports.unknown() }).strict();
|
|
15077
16299
|
var apiMethods = /* @__PURE__ */ new Set([
|
|
@@ -15126,8 +16348,7 @@ var RpcRouter = class {
|
|
|
15126
16348
|
method: request.payload.method,
|
|
15127
16349
|
durationMs: Math.round(performance.now() - startedAt),
|
|
15128
16350
|
code: response.payload.code,
|
|
15129
|
-
retryable: response.payload.retryable
|
|
15130
|
-
reason: diagnosticReason3(error)
|
|
16351
|
+
retryable: response.payload.retryable
|
|
15131
16352
|
});
|
|
15132
16353
|
return response;
|
|
15133
16354
|
} finally {
|
|
@@ -15165,22 +16386,14 @@ var RpcRouter = class {
|
|
|
15165
16386
|
}
|
|
15166
16387
|
}
|
|
15167
16388
|
};
|
|
15168
|
-
var RpcError = class extends Error {
|
|
15169
|
-
constructor(code, message, details, retryable = false) {
|
|
15170
|
-
super(message);
|
|
15171
|
-
this.code = code;
|
|
15172
|
-
this.details = details;
|
|
15173
|
-
this.retryable = retryable;
|
|
15174
|
-
}
|
|
15175
|
-
};
|
|
15176
16389
|
function errorResponse(requestId, error) {
|
|
15177
|
-
|
|
15178
|
-
if (error instanceof
|
|
15179
|
-
return createRpcError(
|
|
15180
|
-
|
|
15181
|
-
|
|
15182
|
-
|
|
15183
|
-
|
|
16390
|
+
const code = safeErrorCode(error);
|
|
16391
|
+
if (error instanceof RpcError) return createRpcError(requestId, code, error.message, error.details, error.retryable);
|
|
16392
|
+
return createRpcError(
|
|
16393
|
+
requestId,
|
|
16394
|
+
code,
|
|
16395
|
+
code === "INVALID_MESSAGE" ? "The RPC parameters are invalid." : "The Host could not complete the request."
|
|
16396
|
+
);
|
|
15184
16397
|
}
|
|
15185
16398
|
|
|
15186
16399
|
// src/file-viewer-contract.ts
|
|
@@ -15304,7 +16517,7 @@ function isOversizedListing(value) {
|
|
|
15304
16517
|
}
|
|
15305
16518
|
|
|
15306
16519
|
// src/server-connection.ts
|
|
15307
|
-
var DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS =
|
|
16520
|
+
var DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 12e3;
|
|
15308
16521
|
var HostServerConnection = class {
|
|
15309
16522
|
constructor(config, identity, identities, api, connections, logger, createWebSocket = (url) => new WebSocket(url), rtcFactoryProvider, hostCapabilities = () => ["harness.api.v1"], harnessVersion) {
|
|
15310
16523
|
this.config = config;
|
|
@@ -15467,7 +16680,7 @@ var HostServerConnection = class {
|
|
|
15467
16680
|
this.terminalError = code;
|
|
15468
16681
|
this.logger.error("server control frame failed", {
|
|
15469
16682
|
code,
|
|
15470
|
-
reason:
|
|
16683
|
+
reason: diagnosticReason3(error)
|
|
15471
16684
|
});
|
|
15472
16685
|
socket.close(4008, "invalid control frame");
|
|
15473
16686
|
});
|
|
@@ -15594,6 +16807,7 @@ var HostServerConnection = class {
|
|
|
15594
16807
|
connectionId: payload.connectionId,
|
|
15595
16808
|
membershipId: descriptor.membershipId,
|
|
15596
16809
|
peer,
|
|
16810
|
+
preferredTransports: payload.preferredTransports,
|
|
15597
16811
|
noise,
|
|
15598
16812
|
transport: "negotiating"
|
|
15599
16813
|
});
|
|
@@ -15683,13 +16897,24 @@ var HostServerConnection = class {
|
|
|
15683
16897
|
code: errorCode(error)
|
|
15684
16898
|
});
|
|
15685
16899
|
}
|
|
16900
|
+
if (!tunnel.preferredTransports.includes("turn")) iceServers = stunOnlyIceServers(iceServers);
|
|
15686
16901
|
const rtc = new RtcDataChannelTransport({
|
|
15687
16902
|
role: "responder",
|
|
15688
16903
|
factory: this.rtcFactory,
|
|
15689
16904
|
iceServers,
|
|
15690
16905
|
onSignal: (signal) => this.sendRtcSignal(tunnel, signal),
|
|
15691
16906
|
negotiateTimeoutMs: DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS,
|
|
15692
|
-
label: `host<-${tunnel.peer.deviceId}
|
|
16907
|
+
label: `host<-${tunnel.peer.deviceId}`,
|
|
16908
|
+
onDiagnostic: (event) => {
|
|
16909
|
+
if (event.type !== "local-candidate-filtered" && event.type !== "candidate-pair-filtered") return;
|
|
16910
|
+
this.logger.debug("webrtc candidate filtered", {
|
|
16911
|
+
connectionId: shortId3(tunnel.connectionId),
|
|
16912
|
+
peerDeviceId: shortId3(tunnel.peer.deviceId),
|
|
16913
|
+
type: event.type,
|
|
16914
|
+
reason: event.reason,
|
|
16915
|
+
candidate: event.type === "local-candidate-filtered" ? event.candidate : event.localCandidate
|
|
16916
|
+
});
|
|
16917
|
+
}
|
|
15693
16918
|
});
|
|
15694
16919
|
tunnel.rtc = rtc;
|
|
15695
16920
|
rtc.onMessage((data) => tunnel.channel?.receive(void 0, data));
|
|
@@ -15731,7 +16956,8 @@ var HostServerConnection = class {
|
|
|
15731
16956
|
this.logger.info("webrtc data channel ready", {
|
|
15732
16957
|
connectionId: shortId3(tunnel.connectionId),
|
|
15733
16958
|
peerDeviceId: shortId3(tunnel.peer.deviceId),
|
|
15734
|
-
transport: tunnel.transportMode ?? selected
|
|
16959
|
+
transport: tunnel.transportMode ?? selected,
|
|
16960
|
+
...webrtcDiagnosticsLogFields2(rtcDiagnostics(tunnel.rtc))
|
|
15735
16961
|
});
|
|
15736
16962
|
}
|
|
15737
16963
|
async handleRtcFailed(tunnel, rtc, error) {
|
|
@@ -15739,7 +16965,8 @@ var HostServerConnection = class {
|
|
|
15739
16965
|
if (tunnel.transport === "p2p" || tunnel.transport === "turn") {
|
|
15740
16966
|
this.logger.warn("webrtc data channel failed; disconnecting peer", {
|
|
15741
16967
|
connectionId: shortId3(tunnel.connectionId),
|
|
15742
|
-
reason:
|
|
16968
|
+
reason: diagnosticReason3(error),
|
|
16969
|
+
...webrtcDiagnosticsLogFields2(rtcDiagnostics(rtc))
|
|
15743
16970
|
});
|
|
15744
16971
|
await this.dropTunnel(tunnel.connectionId, "CONNECTION_FAILED");
|
|
15745
16972
|
return;
|
|
@@ -15749,7 +16976,8 @@ var HostServerConnection = class {
|
|
|
15749
16976
|
await rtc.close();
|
|
15750
16977
|
this.logger.warn("webrtc negotiation failed; falling back to relay", {
|
|
15751
16978
|
connectionId: shortId3(tunnel.connectionId),
|
|
15752
|
-
reason:
|
|
16979
|
+
reason: diagnosticReason3(error),
|
|
16980
|
+
...webrtcDiagnosticsLogFields2(rtcDiagnostics(rtc))
|
|
15753
16981
|
});
|
|
15754
16982
|
}
|
|
15755
16983
|
sendTransportSelected(tunnel, transport) {
|
|
@@ -15793,7 +17021,7 @@ var HostServerConnection = class {
|
|
|
15793
17021
|
} catch (error) {
|
|
15794
17022
|
this.logger.warn("remote connection RTC cleanup failed", {
|
|
15795
17023
|
connectionId: shortId3(connectionId),
|
|
15796
|
-
reason:
|
|
17024
|
+
reason: diagnosticReason3(error)
|
|
15797
17025
|
});
|
|
15798
17026
|
}
|
|
15799
17027
|
if (tunnel.channel !== void 0) {
|
|
@@ -15804,7 +17032,7 @@ var HostServerConnection = class {
|
|
|
15804
17032
|
await tunnel.channel.close(code).catch(() => void 0);
|
|
15805
17033
|
this.logger.warn("remote connection channel cleanup failed", {
|
|
15806
17034
|
connectionId: shortId3(connectionId),
|
|
15807
|
-
reason:
|
|
17035
|
+
reason: diagnosticReason3(error)
|
|
15808
17036
|
});
|
|
15809
17037
|
}
|
|
15810
17038
|
} else {
|
|
@@ -15972,10 +17200,32 @@ function shortId3(value) {
|
|
|
15972
17200
|
function asError2(error) {
|
|
15973
17201
|
return error instanceof Error ? error : new Error("Unknown Server connection error.");
|
|
15974
17202
|
}
|
|
15975
|
-
function
|
|
17203
|
+
function diagnosticReason3(error) {
|
|
15976
17204
|
const message = asError2(error).message.replace(/[\r\n]+/g, " ").slice(0, 160);
|
|
15977
17205
|
return message || "Unknown Server connection error.";
|
|
15978
17206
|
}
|
|
17207
|
+
function webrtcDiagnosticsLogFields2(diagnostics) {
|
|
17208
|
+
if (diagnostics === void 0) return {};
|
|
17209
|
+
return {
|
|
17210
|
+
rtcConnectionState: diagnostics.connectionState,
|
|
17211
|
+
rtcIceConnectionState: diagnostics.iceConnectionState,
|
|
17212
|
+
rtcIceGatheringState: diagnostics.iceGatheringState,
|
|
17213
|
+
rtcLocalCandidates: diagnostics.localCandidates,
|
|
17214
|
+
rtcRemoteCandidates: diagnostics.remoteCandidates,
|
|
17215
|
+
rtcCandidatePairs: diagnostics.candidatePairs,
|
|
17216
|
+
rtcFilteredLocalCandidates: diagnostics.filteredLocalCandidates,
|
|
17217
|
+
rtcFilteredCandidatePairs: diagnostics.filteredCandidatePairs,
|
|
17218
|
+
...diagnostics.selectedPath === void 0 ? {} : { rtcSelectedPath: diagnostics.selectedPath }
|
|
17219
|
+
};
|
|
17220
|
+
}
|
|
17221
|
+
function rtcDiagnostics(rtc) {
|
|
17222
|
+
try {
|
|
17223
|
+
const candidate = rtc;
|
|
17224
|
+
return typeof candidate.diagnostics === "function" ? candidate.diagnostics() : void 0;
|
|
17225
|
+
} catch {
|
|
17226
|
+
return void 0;
|
|
17227
|
+
}
|
|
17228
|
+
}
|
|
15979
17229
|
function errorCode(error) {
|
|
15980
17230
|
return error instanceof ServerApiError || error instanceof ControlConnectionError ? error.code : "CONNECTION_FAILED";
|
|
15981
17231
|
}
|
|
@@ -15993,7 +17243,7 @@ function closeCode(code) {
|
|
|
15993
17243
|
// src/remote-directory-browser.ts
|
|
15994
17244
|
import { readdir, stat as stat3 } from "node:fs/promises";
|
|
15995
17245
|
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
15996
|
-
import { basename, dirname as
|
|
17246
|
+
import { basename, dirname as dirname5, isAbsolute as isAbsolute2, parse, resolve } from "node:path";
|
|
15997
17247
|
var MAX_ENTRIES = 500;
|
|
15998
17248
|
async function listRemoteDirectory(path, signal) {
|
|
15999
17249
|
signal?.throwIfAborted();
|
|
@@ -16026,7 +17276,7 @@ function crumbs(path) {
|
|
|
16026
17276
|
let current = path;
|
|
16027
17277
|
while (current !== root) {
|
|
16028
17278
|
segments.unshift(basename(current));
|
|
16029
|
-
current =
|
|
17279
|
+
current = dirname5(current);
|
|
16030
17280
|
}
|
|
16031
17281
|
for (const segment of segments) {
|
|
16032
17282
|
current = resolve(current, segment);
|
|
@@ -16249,7 +17499,7 @@ var HarnessApiBridge = class {
|
|
|
16249
17499
|
method: params.method,
|
|
16250
17500
|
durationMs,
|
|
16251
17501
|
timedOut: signal.aborted,
|
|
16252
|
-
|
|
17502
|
+
code: safeErrorCode(error)
|
|
16253
17503
|
});
|
|
16254
17504
|
throw error;
|
|
16255
17505
|
}
|
|
@@ -16698,10 +17948,6 @@ function deniedSettingsNamespace(ns) {
|
|
|
16698
17948
|
function deniedMethod(method) {
|
|
16699
17949
|
return new RpcError("METHOD_NOT_ALLOWED", `Harness API method ${JSON.stringify(method)} is not available in remote mode.`);
|
|
16700
17950
|
}
|
|
16701
|
-
function diagnosticReason5(error) {
|
|
16702
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
16703
|
-
return message.replace(/[\r\n]+/g, " ").slice(0, 160) || "Unknown Harness API failure.";
|
|
16704
|
-
}
|
|
16705
17951
|
function frameSessionId(frame) {
|
|
16706
17952
|
const payload = frame.payload;
|
|
16707
17953
|
return typeof payload.sessionId === "string" && payload.sessionId.length > 0 ? payload.sessionId : void 0;
|
|
@@ -16954,7 +18200,7 @@ var HostPluginRuntime = class {
|
|
|
16954
18200
|
this.connections,
|
|
16955
18201
|
this.logger,
|
|
16956
18202
|
void 0,
|
|
16957
|
-
this.config.forceRelay ? void 0 :
|
|
18203
|
+
this.config.forceRelay ? void 0 : () => loadNodeRtcFactory({ routeTargets: this.config.serverUrl === void 0 ? [] : [this.config.serverUrl] }),
|
|
16958
18204
|
() => this.fileViewerHost?.() === void 0 ? ["harness.api.v1"] : ["harness.api.v1", "fileviewer.read.v1"],
|
|
16959
18205
|
this.harnessVersion
|
|
16960
18206
|
);
|
|
@@ -17053,6 +18299,7 @@ function decodeBase64(value) {
|
|
|
17053
18299
|
|
|
17054
18300
|
// src/index.ts
|
|
17055
18301
|
var name = "ds-harness-remote";
|
|
18302
|
+
var legacyLoaderModuleNames = /* @__PURE__ */ new Set(["dsh-remote", "@dsh-remote/plugin"]);
|
|
17056
18303
|
function apply(ctx, input = {}) {
|
|
17057
18304
|
ctx.inject(["settings", "apiProxy", "connection", "typertGateway"], (runtimeContext) => activate(runtimeContext, input));
|
|
17058
18305
|
}
|
|
@@ -17085,6 +18332,7 @@ async function activate(ctx, input) {
|
|
|
17085
18332
|
console.error(message);
|
|
17086
18333
|
}
|
|
17087
18334
|
}, config.logLevel);
|
|
18335
|
+
await disableLegacyLoaderEntries(ctx, logger);
|
|
17088
18336
|
const defaultIdentityDirectory = new IdentityStore().directory;
|
|
17089
18337
|
const hostIdentities = new IdentityStore({
|
|
17090
18338
|
directory: config.serverUrl === void 0 ? defaultIdentityDirectory : serverStorageDirectory(defaultIdentityDirectory, config.serverUrl, "host")
|
|
@@ -17146,6 +18394,29 @@ async function activate(ctx, input) {
|
|
|
17146
18394
|
};
|
|
17147
18395
|
}, "dsh-remote lifecycle");
|
|
17148
18396
|
}
|
|
18397
|
+
async function disableLegacyLoaderEntries(ctx, logger) {
|
|
18398
|
+
const loader = ctx.get("loader");
|
|
18399
|
+
if (!isLoaderLike(loader)) return;
|
|
18400
|
+
const currentEntryId = loader.locate?.(ctx.fiber);
|
|
18401
|
+
for (const entry of loader.entries()) {
|
|
18402
|
+
const moduleName = entry.options.name;
|
|
18403
|
+
if (moduleName === void 0 || !legacyLoaderModuleNames.has(moduleName)) continue;
|
|
18404
|
+
if (entry.id === currentEntryId || entry.options.disabled === true) continue;
|
|
18405
|
+
try {
|
|
18406
|
+
await loader.update(entry.id, { disabled: true });
|
|
18407
|
+
logger.warn("disabled legacy loader entry", { entryId: entry.id, moduleName });
|
|
18408
|
+
} catch {
|
|
18409
|
+
logger.warn("failed to disable legacy loader entry", {
|
|
18410
|
+
entryId: entry.id,
|
|
18411
|
+
moduleName,
|
|
18412
|
+
code: "LOADER_UPDATE_FAILED"
|
|
18413
|
+
});
|
|
18414
|
+
}
|
|
18415
|
+
}
|
|
18416
|
+
}
|
|
18417
|
+
function isLoaderLike(value) {
|
|
18418
|
+
return typeof value === "object" && value !== null && typeof value.entries === "function" && typeof value.update === "function";
|
|
18419
|
+
}
|
|
17149
18420
|
export {
|
|
17150
18421
|
ApiProxySwitch,
|
|
17151
18422
|
ClientModeError,
|