ds-harness-remote 0.3.31 → 0.3.33
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 +5 -5
- package/cordis.patch.yml +1 -1
- package/dist/client-runtime.d.ts +2 -1
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +74 -31
- package/dist/client-runtime.js.map +1 -1
- package/dist/client.github.js +90 -39
- package/dist/client.js +89 -38
- package/dist/client.js.map +1 -1
- package/dist/control-runtime.d.ts.map +1 -1
- package/dist/control-runtime.js +1 -0
- package/dist/control-runtime.js.map +1 -1
- package/dist/harness-api-bridge.d.ts +2 -1
- package/dist/harness-api-bridge.d.ts.map +1 -1
- package/dist/harness-api-bridge.js +30 -2
- package/dist/harness-api-bridge.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1372 -94
- 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/server-api.d.ts +2 -0
- package/dist/server-api.d.ts.map +1 -1
- package/dist/server-api.js +3 -0
- package/dist/server-api.js.map +1 -1
- 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 +6 -3
- 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/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.33";
|
|
13216
13538
|
|
|
13217
13539
|
// src/server-api.ts
|
|
13218
13540
|
var HostServerApi = class {
|
|
@@ -13226,9 +13548,13 @@ var HostServerApi = class {
|
|
|
13226
13548
|
identity;
|
|
13227
13549
|
credentials;
|
|
13228
13550
|
credentialsPromise;
|
|
13551
|
+
harnessVersion;
|
|
13229
13552
|
bindIdentity(identity) {
|
|
13230
13553
|
this.identity = identity;
|
|
13231
13554
|
}
|
|
13555
|
+
setHarnessVersion(version) {
|
|
13556
|
+
this.harnessVersion = version;
|
|
13557
|
+
}
|
|
13232
13558
|
currentAuthorization() {
|
|
13233
13559
|
if (this.credentials === void 0) return void 0;
|
|
13234
13560
|
return {
|
|
@@ -13435,7 +13761,8 @@ var HostServerApi = class {
|
|
|
13435
13761
|
role: this.role,
|
|
13436
13762
|
platform: platform(),
|
|
13437
13763
|
identityKey: identity.publicKey,
|
|
13438
|
-
clientVersion: PLUGIN_VERSION
|
|
13764
|
+
clientVersion: PLUGIN_VERSION,
|
|
13765
|
+
...this.role === "host" && this.harnessVersion !== void 0 ? { harnessVersion: this.harnessVersion } : {}
|
|
13439
13766
|
};
|
|
13440
13767
|
}
|
|
13441
13768
|
saveTokens(identity, tokens, authorization) {
|
|
@@ -13646,33 +13973,662 @@ function isRemoteCommandMethod(method) {
|
|
|
13646
13973
|
}
|
|
13647
13974
|
|
|
13648
13975
|
// src/werift-rtc.ts
|
|
13976
|
+
import { createSocket } from "node:dgram";
|
|
13649
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
|
|
13650
14476
|
var cachedFactory;
|
|
13651
|
-
|
|
13652
|
-
|
|
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;
|
|
13653
14486
|
try {
|
|
13654
|
-
const werift = await import("werift");
|
|
13655
|
-
|
|
13656
|
-
|
|
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;
|
|
13657
14493
|
} catch {
|
|
13658
14494
|
return void 0;
|
|
13659
14495
|
}
|
|
13660
14496
|
}
|
|
13661
|
-
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 = {}) {
|
|
13662
14591
|
return {
|
|
13663
14592
|
create(configuration) {
|
|
13664
|
-
const
|
|
14593
|
+
const hostIpv4Candidates = detectHostIpv4Candidates(
|
|
14594
|
+
options.interfaces ?? networkInterfaces(),
|
|
14595
|
+
options.preferredHostIpv4Candidates
|
|
14596
|
+
);
|
|
14597
|
+
const allowedHostIpv4 = new Set(hostIpv4Candidates);
|
|
13665
14598
|
const raw = new werift.RTCPeerConnection({
|
|
13666
|
-
iceServers: configuration.iceServers ?? [],
|
|
14599
|
+
iceServers: orderIceServersForWerift(configuration.iceServers ?? []),
|
|
14600
|
+
iceUseIpv4: true,
|
|
13667
14601
|
iceUseIpv6: false,
|
|
13668
14602
|
iceUseLinkLocalAddress: false,
|
|
13669
|
-
...
|
|
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
|
+
}
|
|
13670
14617
|
});
|
|
13671
14618
|
let onIceCandidate = null;
|
|
13672
14619
|
let onDataChannel = null;
|
|
13673
14620
|
raw.onicecandidate = (event) => {
|
|
13674
14621
|
if (onIceCandidate === null) return;
|
|
13675
|
-
|
|
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 });
|
|
13676
14632
|
};
|
|
13677
14633
|
raw.ondatachannel = (event) => {
|
|
13678
14634
|
if (onDataChannel === null) return;
|
|
@@ -13721,8 +14677,8 @@ function buildWeriftFactory(werift) {
|
|
|
13721
14677
|
get ondatachannel() {
|
|
13722
14678
|
return onDataChannel;
|
|
13723
14679
|
},
|
|
13724
|
-
createDataChannel(label,
|
|
13725
|
-
return adaptDataChannel2(raw.createDataChannel(label, { ordered:
|
|
14680
|
+
createDataChannel(label, options2) {
|
|
14681
|
+
return adaptDataChannel2(raw.createDataChannel(label, { ordered: options2?.ordered ?? true }));
|
|
13726
14682
|
},
|
|
13727
14683
|
createOffer() {
|
|
13728
14684
|
return raw.createOffer();
|
|
@@ -13804,27 +14760,245 @@ function adaptDataChannel2(raw) {
|
|
|
13804
14760
|
}
|
|
13805
14761
|
};
|
|
13806
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
|
+
}
|
|
13807
14823
|
function toArrayBuffer2(data) {
|
|
13808
14824
|
if (typeof data === "string") return data;
|
|
13809
14825
|
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
13810
14826
|
}
|
|
13811
|
-
function
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
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;
|
|
13816
14850
|
for (const address of addresses ?? []) {
|
|
13817
|
-
if (address.internal || address.family
|
|
14851
|
+
if (address.internal || !isIpv4Family(address.family)) continue;
|
|
13818
14852
|
const ip = address.address;
|
|
13819
|
-
if (
|
|
13820
|
-
|
|
13821
|
-
|
|
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
|
+
});
|
|
13822
14859
|
}
|
|
13823
14860
|
}
|
|
13824
|
-
|
|
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
|
+
}
|
|
14938
|
+
}
|
|
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;
|
|
13825
14995
|
}
|
|
13826
|
-
function
|
|
13827
|
-
|
|
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);
|
|
13828
15002
|
}
|
|
13829
15003
|
function isCgnat(ip) {
|
|
13830
15004
|
const parts = ip.split(".").map(Number);
|
|
@@ -13841,8 +15015,9 @@ function isPrivate(ip) {
|
|
|
13841
15015
|
// src/client-runtime.ts
|
|
13842
15016
|
var REMOTE_COMMAND_LIST_MIN_VERSION = [0, 3, 16];
|
|
13843
15017
|
var REMOTE_FILE_VIEWER_MIN_VERSION = [0, 3, 17];
|
|
15018
|
+
var DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 12e3;
|
|
13844
15019
|
var ClientModeRuntime = class {
|
|
13845
|
-
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider =
|
|
15020
|
+
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory) {
|
|
13846
15021
|
this.config = config;
|
|
13847
15022
|
this.identities = identities;
|
|
13848
15023
|
this.server = server;
|
|
@@ -13879,6 +15054,7 @@ var ClientModeRuntime = class {
|
|
|
13879
15054
|
available: this.config.serverUrl !== void 0,
|
|
13880
15055
|
identityReady: this.identity !== void 0,
|
|
13881
15056
|
deviceId: this.identity?.deviceId,
|
|
15057
|
+
deviceName: this.identity?.name,
|
|
13882
15058
|
serverUrl: this.config.serverUrl,
|
|
13883
15059
|
...this.proxySwitch.status(),
|
|
13884
15060
|
connected: this.connected !== void 0,
|
|
@@ -14077,61 +15253,76 @@ var ClientModeRuntime = class {
|
|
|
14077
15253
|
const presence = await this.server.presenceFor(targetDeviceId);
|
|
14078
15254
|
if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
|
|
14079
15255
|
const credentials = await this.server.authenticate(identity);
|
|
14080
|
-
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);
|
|
14081
15257
|
let webRtcFallback = false;
|
|
14082
|
-
const createTransport = (
|
|
15258
|
+
const createTransport = (attempt) => new AdaptiveTransport(
|
|
14083
15259
|
websocketUrl(this.server.baseUrl),
|
|
14084
15260
|
{
|
|
14085
15261
|
role: "client",
|
|
14086
15262
|
deviceId: identity.deviceId,
|
|
14087
15263
|
accessToken: credentials.accessToken,
|
|
14088
15264
|
targetDeviceId,
|
|
14089
|
-
forceRelay: this.config.forceRelay ||
|
|
14090
|
-
preferredTransports:
|
|
14091
|
-
|
|
14092
|
-
|
|
14093
|
-
|
|
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) => {
|
|
14094
15271
|
webRtcFallback = true;
|
|
14095
|
-
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", {
|
|
14096
15273
|
targetDeviceId: shortId(target.deviceId),
|
|
14097
|
-
|
|
15274
|
+
attempt,
|
|
15275
|
+
reason: diagnosticReason(error),
|
|
15276
|
+
...webrtcDiagnosticsLogFields(diagnostics)
|
|
14098
15277
|
});
|
|
14099
15278
|
}
|
|
14100
15279
|
}
|
|
14101
15280
|
);
|
|
14102
|
-
|
|
14103
|
-
let
|
|
15281
|
+
const attempts = this.config.forceRelay || rtcFactory === void 0 ? ["relay"] : ["direct", "turn", "relay"];
|
|
15282
|
+
let transport;
|
|
15283
|
+
let client;
|
|
14104
15284
|
try {
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
await client.close();
|
|
14109
|
-
transport = createTransport(true);
|
|
15285
|
+
for (const attempt of attempts) {
|
|
15286
|
+
webRtcFallback = false;
|
|
15287
|
+
transport = createTransport(attempt);
|
|
14110
15288
|
client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 6e4);
|
|
14111
15289
|
await client.connect();
|
|
14112
15290
|
signal?.throwIfAborted();
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
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
|
+
}
|
|
14116
15300
|
}
|
|
14117
|
-
client
|
|
14118
|
-
|
|
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;
|
|
14119
15308
|
this.connected = void 0;
|
|
14120
15309
|
this.pendingWorkspaceSelection = void 0;
|
|
14121
15310
|
this.proxySwitch.selectLocal();
|
|
14122
15311
|
this.gatewaySwitch.selectLocal();
|
|
14123
|
-
void
|
|
15312
|
+
void connectedClient.close().catch(() => void 0);
|
|
14124
15313
|
this.logger.warn("remote Harness transport closed; falling back to local mode", {
|
|
14125
15314
|
targetDeviceId: shortId(target.deviceId)
|
|
14126
15315
|
});
|
|
14127
15316
|
});
|
|
15317
|
+
const connectionDetails = await connectedTransport.connectionDetails().catch(() => void 0);
|
|
14128
15318
|
this.logger.info("remote Harness transport ready", {
|
|
14129
15319
|
targetDeviceId: shortId(target.deviceId),
|
|
14130
|
-
transport:
|
|
15320
|
+
transport: connectedClient.getStats().mode,
|
|
15321
|
+
...webrtcDiagnosticsLogFields(connectionDetails?.webRtc?.diagnostics)
|
|
14131
15322
|
});
|
|
14132
|
-
return { client, target, transport, features: remoteHostFeatures(serverDevice.clientVersion) };
|
|
15323
|
+
return { client: connectedClient, target, transport: connectedTransport, features: remoteHostFeatures(serverDevice.clientVersion) };
|
|
14133
15324
|
} catch (error) {
|
|
14134
|
-
await client
|
|
15325
|
+
await client?.close().catch(() => void 0);
|
|
14135
15326
|
throw error;
|
|
14136
15327
|
}
|
|
14137
15328
|
}
|
|
@@ -14278,6 +15469,20 @@ function websocketUrl(baseUrl) {
|
|
|
14278
15469
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/ws/v1/connect`;
|
|
14279
15470
|
return url.toString();
|
|
14280
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
|
+
}
|
|
14281
15486
|
function record(value) {
|
|
14282
15487
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
14283
15488
|
throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
|
|
@@ -14355,6 +15560,14 @@ function diagnosticReason(error) {
|
|
|
14355
15560
|
const message = error.message.replace(/[\r\n\t]+/g, " ").slice(0, 240);
|
|
14356
15561
|
return code === void 0 ? message : `${code}: ${message}`;
|
|
14357
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
|
+
}
|
|
14358
15571
|
|
|
14359
15572
|
// src/control-runtime.ts
|
|
14360
15573
|
import { hostname as hostname2 } from "node:os";
|
|
@@ -14363,7 +15576,7 @@ import { hostname as hostname2 } from "node:os";
|
|
|
14363
15576
|
import { createHash } from "node:crypto";
|
|
14364
15577
|
import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
14365
15578
|
import { homedir } from "node:os";
|
|
14366
|
-
import { dirname, join } from "node:path";
|
|
15579
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
14367
15580
|
var identitySchema = external_exports.object({
|
|
14368
15581
|
schemaVersion: external_exports.literal(1),
|
|
14369
15582
|
deviceId: external_exports.string().uuid(),
|
|
@@ -14392,14 +15605,14 @@ var IdentityStore = class {
|
|
|
14392
15605
|
peers = /* @__PURE__ */ new Map();
|
|
14393
15606
|
constructor(options = {}) {
|
|
14394
15607
|
const env = options.env ?? process.env;
|
|
14395
|
-
const dshHome = env.DSH_HOME ||
|
|
14396
|
-
this.directory = options.directory ??
|
|
15608
|
+
const dshHome = env.DSH_HOME || join2(options.homeDirectory ?? homedir(), ".dsh");
|
|
15609
|
+
this.directory = options.directory ?? join2(dshHome, "remote");
|
|
14397
15610
|
}
|
|
14398
15611
|
async loadOrCreate(deviceName) {
|
|
14399
15612
|
await mkdir(this.directory, { recursive: true, mode: 448 });
|
|
14400
15613
|
await chmod(this.directory, 448);
|
|
14401
|
-
const devicePath =
|
|
14402
|
-
const keyPath =
|
|
15614
|
+
const devicePath = join2(this.directory, "device.json");
|
|
15615
|
+
const keyPath = join2(this.directory, "device.key");
|
|
14403
15616
|
const [hasDevice, hasKey] = await Promise.all([exists(devicePath), exists(keyPath)]);
|
|
14404
15617
|
if (hasDevice !== hasKey) {
|
|
14405
15618
|
throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
|
|
@@ -14468,7 +15681,7 @@ var IdentityStore = class {
|
|
|
14468
15681
|
return removed;
|
|
14469
15682
|
}
|
|
14470
15683
|
async loadPeers() {
|
|
14471
|
-
const path =
|
|
15684
|
+
const path = join2(this.directory, "trusted-peers.json");
|
|
14472
15685
|
if (!await exists(path)) {
|
|
14473
15686
|
await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
|
|
14474
15687
|
}
|
|
@@ -14484,7 +15697,7 @@ var IdentityStore = class {
|
|
|
14484
15697
|
this.peers = peers;
|
|
14485
15698
|
}
|
|
14486
15699
|
async savePeers() {
|
|
14487
|
-
await atomicJsonWrite(
|
|
15700
|
+
await atomicJsonWrite(join2(this.directory, "trusted-peers.json"), {
|
|
14488
15701
|
schemaVersion: 1,
|
|
14489
15702
|
peers: [...this.peers.values()]
|
|
14490
15703
|
}, 384);
|
|
@@ -14493,7 +15706,7 @@ var IdentityStore = class {
|
|
|
14493
15706
|
function serverStorageDirectory(root, serverUrl, role) {
|
|
14494
15707
|
const origin = new URL(serverUrl).origin;
|
|
14495
15708
|
const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
|
|
14496
|
-
return
|
|
15709
|
+
return join2(root, "servers", scope, role);
|
|
14497
15710
|
}
|
|
14498
15711
|
function fingerprint(publicKey) {
|
|
14499
15712
|
const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
|
|
@@ -14511,7 +15724,7 @@ async function atomicJsonWrite(path, value, mode) {
|
|
|
14511
15724
|
`, mode);
|
|
14512
15725
|
}
|
|
14513
15726
|
async function atomicTextWrite(path, value, mode) {
|
|
14514
|
-
await mkdir(
|
|
15727
|
+
await mkdir(dirname2(path), { recursive: true, mode: 448 });
|
|
14515
15728
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
14516
15729
|
await writeFile(temporary, value, { encoding: "utf8", mode, flag: "wx" });
|
|
14517
15730
|
await chmod(temporary, mode);
|
|
@@ -14536,7 +15749,7 @@ function safeErrorMessage(error) {
|
|
|
14536
15749
|
|
|
14537
15750
|
// src/server-credentials.ts
|
|
14538
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";
|
|
14539
|
-
import { dirname as
|
|
15752
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
14540
15753
|
var credentialSchema = external_exports.object({
|
|
14541
15754
|
schemaVersion: external_exports.literal(1),
|
|
14542
15755
|
serverUrl: external_exports.string().url(),
|
|
@@ -14551,7 +15764,7 @@ var credentialSchema = external_exports.object({
|
|
|
14551
15764
|
var ServerCredentialStore = class {
|
|
14552
15765
|
path;
|
|
14553
15766
|
constructor(directory) {
|
|
14554
|
-
this.path =
|
|
15767
|
+
this.path = join3(directory, "server-credentials.json");
|
|
14555
15768
|
}
|
|
14556
15769
|
async load(serverUrl, deviceId) {
|
|
14557
15770
|
if (!await exists2(this.path)) return void 0;
|
|
@@ -14578,7 +15791,7 @@ var ServerCredentialsInvalidError = class extends Error {
|
|
|
14578
15791
|
code = "SERVER_CREDENTIALS_INVALID";
|
|
14579
15792
|
};
|
|
14580
15793
|
async function atomicWrite(path, contents) {
|
|
14581
|
-
await mkdir2(
|
|
15794
|
+
await mkdir2(dirname3(path), { recursive: true, mode: 448 });
|
|
14582
15795
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
14583
15796
|
await writeFile2(temporary, contents, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
14584
15797
|
await chmod2(temporary, 384);
|
|
@@ -14788,6 +16001,7 @@ var PluginControlRuntime = class {
|
|
|
14788
16001
|
return {
|
|
14789
16002
|
mode: "local",
|
|
14790
16003
|
available: false,
|
|
16004
|
+
deviceName: hostname2(),
|
|
14791
16005
|
hostAuthorizationAvailable: this.host !== void 0,
|
|
14792
16006
|
...this.host === void 0 ? {} : { host: this.host.hostStatus() }
|
|
14793
16007
|
};
|
|
@@ -15037,7 +16251,7 @@ function shortId2(value) {
|
|
|
15037
16251
|
|
|
15038
16252
|
// src/harness-version.ts
|
|
15039
16253
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
15040
|
-
import { dirname as
|
|
16254
|
+
import { dirname as dirname4, isAbsolute, join as join4 } from "node:path";
|
|
15041
16255
|
var LEGACY_PLACEHOLDER_VERSION = "0.0.1";
|
|
15042
16256
|
function normalizeHarnessVersion(value) {
|
|
15043
16257
|
if (typeof value !== "string") return void 0;
|
|
@@ -15051,14 +16265,14 @@ function selectHarnessVersion(reportedVersion, distributionVersion) {
|
|
|
15051
16265
|
}
|
|
15052
16266
|
async function readHarnessDistributionVersion(entrypoint = process.argv[1]) {
|
|
15053
16267
|
if (entrypoint === void 0 || !isAbsolute(entrypoint)) return void 0;
|
|
15054
|
-
let directory =
|
|
16268
|
+
let directory = dirname4(entrypoint);
|
|
15055
16269
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
15056
16270
|
try {
|
|
15057
|
-
const manifest = JSON.parse(await readFile3(
|
|
16271
|
+
const manifest = JSON.parse(await readFile3(join4(directory, "package.json"), "utf8"));
|
|
15058
16272
|
if (manifest.name === "@deepseek-ai/dsh") return normalizeHarnessVersion(manifest.version);
|
|
15059
16273
|
} catch {
|
|
15060
16274
|
}
|
|
15061
|
-
const parent =
|
|
16275
|
+
const parent = dirname4(directory);
|
|
15062
16276
|
if (parent === directory) break;
|
|
15063
16277
|
directory = parent;
|
|
15064
16278
|
}
|
|
@@ -15297,7 +16511,7 @@ function isOversizedListing(value) {
|
|
|
15297
16511
|
}
|
|
15298
16512
|
|
|
15299
16513
|
// src/server-connection.ts
|
|
15300
|
-
var DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS =
|
|
16514
|
+
var DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 12e3;
|
|
15301
16515
|
var HostServerConnection = class {
|
|
15302
16516
|
constructor(config, identity, identities, api, connections, logger, createWebSocket = (url) => new WebSocket(url), rtcFactoryProvider, hostCapabilities = () => ["harness.api.v1"], harnessVersion) {
|
|
15303
16517
|
this.config = config;
|
|
@@ -15587,6 +16801,7 @@ var HostServerConnection = class {
|
|
|
15587
16801
|
connectionId: payload.connectionId,
|
|
15588
16802
|
membershipId: descriptor.membershipId,
|
|
15589
16803
|
peer,
|
|
16804
|
+
preferredTransports: payload.preferredTransports,
|
|
15590
16805
|
noise,
|
|
15591
16806
|
transport: "negotiating"
|
|
15592
16807
|
});
|
|
@@ -15676,13 +16891,24 @@ var HostServerConnection = class {
|
|
|
15676
16891
|
code: errorCode(error)
|
|
15677
16892
|
});
|
|
15678
16893
|
}
|
|
16894
|
+
if (!tunnel.preferredTransports.includes("turn")) iceServers = stunOnlyIceServers(iceServers);
|
|
15679
16895
|
const rtc = new RtcDataChannelTransport({
|
|
15680
16896
|
role: "responder",
|
|
15681
16897
|
factory: this.rtcFactory,
|
|
15682
16898
|
iceServers,
|
|
15683
16899
|
onSignal: (signal) => this.sendRtcSignal(tunnel, signal),
|
|
15684
16900
|
negotiateTimeoutMs: DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS,
|
|
15685
|
-
label: `host<-${tunnel.peer.deviceId}
|
|
16901
|
+
label: `host<-${tunnel.peer.deviceId}`,
|
|
16902
|
+
onDiagnostic: (event) => {
|
|
16903
|
+
if (event.type !== "local-candidate-filtered" && event.type !== "candidate-pair-filtered") return;
|
|
16904
|
+
this.logger.debug("webrtc candidate filtered", {
|
|
16905
|
+
connectionId: shortId3(tunnel.connectionId),
|
|
16906
|
+
peerDeviceId: shortId3(tunnel.peer.deviceId),
|
|
16907
|
+
type: event.type,
|
|
16908
|
+
reason: event.reason,
|
|
16909
|
+
candidate: event.type === "local-candidate-filtered" ? event.candidate : event.localCandidate
|
|
16910
|
+
});
|
|
16911
|
+
}
|
|
15686
16912
|
});
|
|
15687
16913
|
tunnel.rtc = rtc;
|
|
15688
16914
|
rtc.onMessage((data) => tunnel.channel?.receive(void 0, data));
|
|
@@ -15724,7 +16950,8 @@ var HostServerConnection = class {
|
|
|
15724
16950
|
this.logger.info("webrtc data channel ready", {
|
|
15725
16951
|
connectionId: shortId3(tunnel.connectionId),
|
|
15726
16952
|
peerDeviceId: shortId3(tunnel.peer.deviceId),
|
|
15727
|
-
transport: tunnel.transportMode ?? selected
|
|
16953
|
+
transport: tunnel.transportMode ?? selected,
|
|
16954
|
+
...webrtcDiagnosticsLogFields2(rtcDiagnostics(tunnel.rtc))
|
|
15728
16955
|
});
|
|
15729
16956
|
}
|
|
15730
16957
|
async handleRtcFailed(tunnel, rtc, error) {
|
|
@@ -15732,7 +16959,8 @@ var HostServerConnection = class {
|
|
|
15732
16959
|
if (tunnel.transport === "p2p" || tunnel.transport === "turn") {
|
|
15733
16960
|
this.logger.warn("webrtc data channel failed; disconnecting peer", {
|
|
15734
16961
|
connectionId: shortId3(tunnel.connectionId),
|
|
15735
|
-
reason: diagnosticReason4(error)
|
|
16962
|
+
reason: diagnosticReason4(error),
|
|
16963
|
+
...webrtcDiagnosticsLogFields2(rtcDiagnostics(rtc))
|
|
15736
16964
|
});
|
|
15737
16965
|
await this.dropTunnel(tunnel.connectionId, "CONNECTION_FAILED");
|
|
15738
16966
|
return;
|
|
@@ -15742,7 +16970,8 @@ var HostServerConnection = class {
|
|
|
15742
16970
|
await rtc.close();
|
|
15743
16971
|
this.logger.warn("webrtc negotiation failed; falling back to relay", {
|
|
15744
16972
|
connectionId: shortId3(tunnel.connectionId),
|
|
15745
|
-
reason: diagnosticReason4(error)
|
|
16973
|
+
reason: diagnosticReason4(error),
|
|
16974
|
+
...webrtcDiagnosticsLogFields2(rtcDiagnostics(rtc))
|
|
15746
16975
|
});
|
|
15747
16976
|
}
|
|
15748
16977
|
sendTransportSelected(tunnel, transport) {
|
|
@@ -15969,6 +17198,28 @@ function diagnosticReason4(error) {
|
|
|
15969
17198
|
const message = asError2(error).message.replace(/[\r\n]+/g, " ").slice(0, 160);
|
|
15970
17199
|
return message || "Unknown Server connection error.";
|
|
15971
17200
|
}
|
|
17201
|
+
function webrtcDiagnosticsLogFields2(diagnostics) {
|
|
17202
|
+
if (diagnostics === void 0) return {};
|
|
17203
|
+
return {
|
|
17204
|
+
rtcConnectionState: diagnostics.connectionState,
|
|
17205
|
+
rtcIceConnectionState: diagnostics.iceConnectionState,
|
|
17206
|
+
rtcIceGatheringState: diagnostics.iceGatheringState,
|
|
17207
|
+
rtcLocalCandidates: diagnostics.localCandidates,
|
|
17208
|
+
rtcRemoteCandidates: diagnostics.remoteCandidates,
|
|
17209
|
+
rtcCandidatePairs: diagnostics.candidatePairs,
|
|
17210
|
+
rtcFilteredLocalCandidates: diagnostics.filteredLocalCandidates,
|
|
17211
|
+
rtcFilteredCandidatePairs: diagnostics.filteredCandidatePairs,
|
|
17212
|
+
...diagnostics.selectedPath === void 0 ? {} : { rtcSelectedPath: diagnostics.selectedPath }
|
|
17213
|
+
};
|
|
17214
|
+
}
|
|
17215
|
+
function rtcDiagnostics(rtc) {
|
|
17216
|
+
try {
|
|
17217
|
+
const candidate = rtc;
|
|
17218
|
+
return typeof candidate.diagnostics === "function" ? candidate.diagnostics() : void 0;
|
|
17219
|
+
} catch {
|
|
17220
|
+
return void 0;
|
|
17221
|
+
}
|
|
17222
|
+
}
|
|
15972
17223
|
function errorCode(error) {
|
|
15973
17224
|
return error instanceof ServerApiError || error instanceof ControlConnectionError ? error.code : "CONNECTION_FAILED";
|
|
15974
17225
|
}
|
|
@@ -15986,7 +17237,7 @@ function closeCode(code) {
|
|
|
15986
17237
|
// src/remote-directory-browser.ts
|
|
15987
17238
|
import { readdir, stat as stat3 } from "node:fs/promises";
|
|
15988
17239
|
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
15989
|
-
import { basename, dirname as
|
|
17240
|
+
import { basename, dirname as dirname5, isAbsolute as isAbsolute2, parse, resolve } from "node:path";
|
|
15990
17241
|
var MAX_ENTRIES = 500;
|
|
15991
17242
|
async function listRemoteDirectory(path, signal) {
|
|
15992
17243
|
signal?.throwIfAborted();
|
|
@@ -16019,7 +17270,7 @@ function crumbs(path) {
|
|
|
16019
17270
|
let current = path;
|
|
16020
17271
|
while (current !== root) {
|
|
16021
17272
|
segments.unshift(basename(current));
|
|
16022
|
-
current =
|
|
17273
|
+
current = dirname5(current);
|
|
16023
17274
|
}
|
|
16024
17275
|
for (const segment of segments) {
|
|
16025
17276
|
current = resolve(current, segment);
|
|
@@ -16070,7 +17321,11 @@ var transferReadSchema = external_exports.object({ transferId: transferIdSchema,
|
|
|
16070
17321
|
var transferCloseSchema = external_exports.object({ transferId: transferIdSchema }).strict();
|
|
16071
17322
|
var commandExecuteSchema = external_exports.object({
|
|
16072
17323
|
agentId: external_exports.string().min(1).max(128),
|
|
16073
|
-
line: external_exports.string().min(1).max(2048)
|
|
17324
|
+
line: external_exports.string().min(1).max(2048),
|
|
17325
|
+
// The official Harness command endpoint requires the `images` wire field.
|
|
17326
|
+
// Remote command execution keeps attachments out of scope, so only an empty
|
|
17327
|
+
// list is accepted and legacy clients that omit it are normalized below.
|
|
17328
|
+
images: external_exports.array(external_exports.never()).length(0).optional()
|
|
16074
17329
|
}).strict();
|
|
16075
17330
|
var commandListSchema = external_exports.object({
|
|
16076
17331
|
agentId: external_exports.string().min(1).max(128)
|
|
@@ -16183,11 +17438,12 @@ var API_TRANSFER_IDLE_MS = 2 * 6e4;
|
|
|
16183
17438
|
var INLINE_TRANSFER_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
16184
17439
|
var SESSION_HISTORY_PAGE_SIZES = [50, 30, 20, 12, 6, 3, 1];
|
|
16185
17440
|
var HarnessApiBridge = class {
|
|
16186
|
-
constructor(api, publish, maxStreams = 8, logger, typertGateway) {
|
|
17441
|
+
constructor(api, publish, maxStreams = 8, logger, typertGateway, harnessVersion) {
|
|
16187
17442
|
this.api = api;
|
|
16188
17443
|
this.publish = publish;
|
|
16189
17444
|
this.maxStreams = maxStreams;
|
|
16190
17445
|
this.logger = logger;
|
|
17446
|
+
this.harnessVersion = harnessVersion;
|
|
16191
17447
|
this.methods = createMethodMap(api, typertGateway);
|
|
16192
17448
|
this.mux = api.events.mux.bind(api.events);
|
|
16193
17449
|
this.host = api.events.host.bind(api.events);
|
|
@@ -16225,6 +17481,7 @@ var HarnessApiBridge = class {
|
|
|
16225
17481
|
const value = await listRemoteDirectory(typeof payload.path === "string" ? payload.path : void 0, signal);
|
|
16226
17482
|
response = { rpcId: params.rpcId, result: { ok: true, value } };
|
|
16227
17483
|
}
|
|
17484
|
+
if (params.method === "host.describe") response = normalizeHostDescribeVersion(response, this.harnessVersion);
|
|
16228
17485
|
this.logger?.debug("harness api call ok", {
|
|
16229
17486
|
method: params.method,
|
|
16230
17487
|
durationMs: Math.round(performance.now() - startedAt)
|
|
@@ -16455,6 +17712,24 @@ function needsRemoteDirectoryFallback(response) {
|
|
|
16455
17712
|
const result = response.result;
|
|
16456
17713
|
return typeof result === "object" && result !== null && "ok" in result && result.ok === false && "error" in result && typeof result.error === "object" && result.error !== null && "code" in result.error && result.error.code === "directory-picker-unavailable";
|
|
16457
17714
|
}
|
|
17715
|
+
function normalizeHostDescribeVersion(response, harnessVersion) {
|
|
17716
|
+
if (!response.result.ok || harnessVersion === void 0) return response;
|
|
17717
|
+
const value = response.result.value;
|
|
17718
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return response;
|
|
17719
|
+
const description = value;
|
|
17720
|
+
const selectedVersion = selectHarnessVersion(
|
|
17721
|
+
normalizeHarnessVersion(description.version),
|
|
17722
|
+
harnessVersion
|
|
17723
|
+
);
|
|
17724
|
+
if (selectedVersion === void 0 || description.version === selectedVersion) return response;
|
|
17725
|
+
return {
|
|
17726
|
+
...response,
|
|
17727
|
+
result: {
|
|
17728
|
+
...response.result,
|
|
17729
|
+
value: { ...description, version: selectedVersion }
|
|
17730
|
+
}
|
|
17731
|
+
};
|
|
17732
|
+
}
|
|
16458
17733
|
function createMethodMap(api, typertGateway) {
|
|
16459
17734
|
const domains = api;
|
|
16460
17735
|
const methods = /* @__PURE__ */ new Map();
|
|
@@ -16464,7 +17739,8 @@ function createMethodMap(api, typertGateway) {
|
|
|
16464
17739
|
const [namespace, commandMethod] = method.split(".");
|
|
16465
17740
|
const implementation2 = async (request, signal) => {
|
|
16466
17741
|
if (commandMethod === "execute") {
|
|
16467
|
-
const
|
|
17742
|
+
const payload = commandExecuteSchema.parse(request.payload);
|
|
17743
|
+
const args2 = { ...payload, images: payload.images ?? [] };
|
|
16468
17744
|
const value2 = await typertGateway.invoke({
|
|
16469
17745
|
namespace,
|
|
16470
17746
|
method: "execute",
|
|
@@ -16775,7 +18051,8 @@ var HostPluginRuntime = class {
|
|
|
16775
18051
|
(event, data) => send(createEvent(event, data)),
|
|
16776
18052
|
void 0,
|
|
16777
18053
|
this.logger,
|
|
16778
|
-
typertGateway?.()
|
|
18054
|
+
typertGateway?.(),
|
|
18055
|
+
this.harnessVersion
|
|
16779
18056
|
);
|
|
16780
18057
|
const fileViewer = new RemoteFileViewerBridge(
|
|
16781
18058
|
() => this.fileViewerHost?.(),
|
|
@@ -16803,6 +18080,7 @@ var HostPluginRuntime = class {
|
|
|
16803
18080
|
});
|
|
16804
18081
|
if (this.serverApi !== void 0) {
|
|
16805
18082
|
this.harnessVersion = await this.readHarnessVersion();
|
|
18083
|
+
this.serverApi.setHarnessVersion(this.harnessVersion);
|
|
16806
18084
|
this.serverApi.bindIdentity(this.identity);
|
|
16807
18085
|
this.serverConnection = this.createServerConnection(this.identity);
|
|
16808
18086
|
this.serverConnection.start();
|
|
@@ -16920,7 +18198,7 @@ var HostPluginRuntime = class {
|
|
|
16920
18198
|
this.connections,
|
|
16921
18199
|
this.logger,
|
|
16922
18200
|
void 0,
|
|
16923
|
-
this.config.forceRelay ? void 0 :
|
|
18201
|
+
this.config.forceRelay ? void 0 : () => loadNodeRtcFactory({ routeTargets: this.config.serverUrl === void 0 ? [] : [this.config.serverUrl] }),
|
|
16924
18202
|
() => this.fileViewerHost?.() === void 0 ? ["harness.api.v1"] : ["harness.api.v1", "fileviewer.read.v1"],
|
|
16925
18203
|
this.harnessVersion
|
|
16926
18204
|
);
|
|
@@ -17018,13 +18296,13 @@ function decodeBase64(value) {
|
|
|
17018
18296
|
}
|
|
17019
18297
|
|
|
17020
18298
|
// src/index.ts
|
|
17021
|
-
var name = "
|
|
18299
|
+
var name = "ds-harness-remote";
|
|
17022
18300
|
function apply(ctx, input = {}) {
|
|
17023
18301
|
ctx.inject(["settings", "apiProxy", "connection", "typertGateway"], (runtimeContext) => activate(runtimeContext, input));
|
|
17024
18302
|
}
|
|
17025
18303
|
async function activate(ctx, input) {
|
|
17026
18304
|
const settings = ctx.get("settings");
|
|
17027
|
-
const settingsScope = settings?.register(settingsNamespace("
|
|
18305
|
+
const settingsScope = settings?.register(settingsNamespace("ds-harness-remote"), Config, {
|
|
17028
18306
|
base: input,
|
|
17029
18307
|
applies: "restart",
|
|
17030
18308
|
validate: (value) => {
|