ds-harness-remote 0.3.32 → 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 +3 -3
- package/dist/client-runtime.d.ts +2 -1
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +73 -31
- package/dist/client-runtime.js.map +1 -1
- package/dist/client.github.js +78 -48
- package/dist/client.js +78 -48
- package/dist/client.js.map +1 -1
- package/dist/index.js +1331 -87
- 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-connection.d.ts.map +1 -1
- package/dist/server-connection.js +43 -2
- package/dist/server-connection.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +4 -2
- package/dist/service.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/werift-rtc.d.ts +45 -9
- package/dist/werift-rtc.d.ts.map +1 -1
- package/dist/werift-rtc.js +356 -35
- package/dist/werift-rtc.js.map +1 -1
- package/package.json +4 -1
package/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 {
|
|
@@ -13651,33 +13973,662 @@ function isRemoteCommandMethod(method) {
|
|
|
13651
13973
|
}
|
|
13652
13974
|
|
|
13653
13975
|
// src/werift-rtc.ts
|
|
13976
|
+
import { createSocket } from "node:dgram";
|
|
13654
13977
|
import { networkInterfaces } from "node:os";
|
|
13978
|
+
|
|
13979
|
+
// src/native-rtc-helper.ts
|
|
13980
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
13981
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
13982
|
+
import { delimiter, dirname, join } from "node:path";
|
|
13983
|
+
import { fileURLToPath } from "node:url";
|
|
13984
|
+
var cachedExternalFactory;
|
|
13985
|
+
var cachedExternalFactoryResolved = false;
|
|
13986
|
+
var cachedNodeBinary;
|
|
13987
|
+
var cachedNodeBinaryResolved = false;
|
|
13988
|
+
async function loadExternalNativeRtcFactory() {
|
|
13989
|
+
if (cachedExternalFactory !== void 0) return cachedExternalFactory;
|
|
13990
|
+
if (cachedExternalFactoryResolved) return void 0;
|
|
13991
|
+
cachedExternalFactoryResolved = true;
|
|
13992
|
+
const nodeBinary = resolveExternalNodeBinaryForRtc();
|
|
13993
|
+
if (nodeBinary === void 0) return void 0;
|
|
13994
|
+
cachedExternalFactory = buildExternalNativeRtcFactory(nodeBinary);
|
|
13995
|
+
return cachedExternalFactory;
|
|
13996
|
+
}
|
|
13997
|
+
function buildExternalNativeRtcFactory(nodeBinary, requireFrom = fileURLToPath(import.meta.url)) {
|
|
13998
|
+
return {
|
|
13999
|
+
create(configuration) {
|
|
14000
|
+
return new ExternalNativePeerConnection(nodeBinary, requireFrom, configuration);
|
|
14001
|
+
}
|
|
14002
|
+
};
|
|
14003
|
+
}
|
|
14004
|
+
function resolveExternalNodeBinaryForRtc() {
|
|
14005
|
+
if (cachedNodeBinaryResolved) return cachedNodeBinary;
|
|
14006
|
+
cachedNodeBinaryResolved = true;
|
|
14007
|
+
const requireFrom = fileURLToPath(import.meta.url);
|
|
14008
|
+
for (const candidate of nodeBinaryCandidates()) {
|
|
14009
|
+
if (isUsableExternalNode(candidate, requireFrom)) {
|
|
14010
|
+
cachedNodeBinary = candidate;
|
|
14011
|
+
return candidate;
|
|
14012
|
+
}
|
|
14013
|
+
}
|
|
14014
|
+
return void 0;
|
|
14015
|
+
}
|
|
14016
|
+
function nodeBinaryCandidates() {
|
|
14017
|
+
const candidates = [];
|
|
14018
|
+
const add3 = (value) => {
|
|
14019
|
+
if (value === void 0 || value.trim() === "") return;
|
|
14020
|
+
if (!candidates.includes(value)) candidates.push(value);
|
|
14021
|
+
};
|
|
14022
|
+
add3(process.env.DSH_REMOTE_NODE);
|
|
14023
|
+
add3(process.env.NODE);
|
|
14024
|
+
add3(process.execPath);
|
|
14025
|
+
for (const part of (process.env.PATH ?? "").split(delimiter)) add3(join(part, process.platform === "win32" ? "node.exe" : "node"));
|
|
14026
|
+
add3("/opt/homebrew/bin/node");
|
|
14027
|
+
add3("/usr/local/bin/node");
|
|
14028
|
+
add3("/usr/bin/node");
|
|
14029
|
+
add3(join(process.env.HOME ?? "", ".volta", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
14030
|
+
add3(join(process.env.HOME ?? "", ".asdf", "shims", process.platform === "win32" ? "node.exe" : "node"));
|
|
14031
|
+
add3(join(process.env.HOME ?? "", ".local", "bin", process.platform === "win32" ? "node.exe" : "node"));
|
|
14032
|
+
for (const nvmNode of nvmNodeCandidates()) add3(nvmNode);
|
|
14033
|
+
return candidates;
|
|
14034
|
+
}
|
|
14035
|
+
function nvmNodeCandidates() {
|
|
14036
|
+
const root = join(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
14037
|
+
if (root === "" || !existsSync(root)) return [];
|
|
14038
|
+
try {
|
|
14039
|
+
return readdirSync(root).map((version) => join(root, version, "bin", process.platform === "win32" ? "node.exe" : "node")).sort((left, right) => right.localeCompare(left, "en", { numeric: true }));
|
|
14040
|
+
} catch {
|
|
14041
|
+
return [];
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14044
|
+
function isUsableExternalNode(candidate, requireFrom) {
|
|
14045
|
+
if (!isExecutableFile(candidate)) return false;
|
|
14046
|
+
const env = { ...process.env, DSH_REMOTE_RTC_HELPER_REQUIRE_FROM: requireFrom };
|
|
14047
|
+
delete env.ELECTRON_RUN_AS_NODE;
|
|
14048
|
+
const probe = spawnSync(candidate, [
|
|
14049
|
+
"--input-type=module",
|
|
14050
|
+
"--eval",
|
|
14051
|
+
[
|
|
14052
|
+
"import { createRequire } from 'node:module';",
|
|
14053
|
+
"const require = createRequire(process.env.DSH_REMOTE_RTC_HELPER_REQUIRE_FROM);",
|
|
14054
|
+
"if (process.versions.electron) throw new Error('electron-node');",
|
|
14055
|
+
"const wrtc = require('@roamhq/wrtc');",
|
|
14056
|
+
"const RTCPeerConnection = wrtc.RTCPeerConnection ?? wrtc.default?.RTCPeerConnection;",
|
|
14057
|
+
"if (typeof RTCPeerConnection !== 'function') throw new Error('missing-rtc');",
|
|
14058
|
+
"console.log(`node:${process.versions.node}`);"
|
|
14059
|
+
].join("")
|
|
14060
|
+
], {
|
|
14061
|
+
cwd: dirname(requireFrom),
|
|
14062
|
+
env,
|
|
14063
|
+
encoding: "utf8",
|
|
14064
|
+
timeout: 3e3
|
|
14065
|
+
});
|
|
14066
|
+
return probe.status === 0 && probe.stdout.startsWith("node:");
|
|
14067
|
+
}
|
|
14068
|
+
function isExecutableFile(candidate) {
|
|
14069
|
+
try {
|
|
14070
|
+
const stat4 = statSync(candidate);
|
|
14071
|
+
if (!stat4.isFile()) return false;
|
|
14072
|
+
if (process.platform === "win32") return true;
|
|
14073
|
+
return (stat4.mode & 73) !== 0;
|
|
14074
|
+
} catch {
|
|
14075
|
+
return false;
|
|
14076
|
+
}
|
|
14077
|
+
}
|
|
14078
|
+
var ExternalNativePeerConnection = class {
|
|
14079
|
+
constructor(nodeBinary, requireFrom, configuration) {
|
|
14080
|
+
this.nodeBinary = nodeBinary;
|
|
14081
|
+
this.requireFrom = requireFrom;
|
|
14082
|
+
this.child = spawn(this.nodeBinary, ["--input-type=module", "--eval", HELPER_SOURCE], {
|
|
14083
|
+
cwd: dirname(this.requireFrom),
|
|
14084
|
+
env: { ...process.env, DSH_REMOTE_RTC_HELPER_REQUIRE_FROM: this.requireFrom },
|
|
14085
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
14086
|
+
});
|
|
14087
|
+
this.child.stdout.setEncoding("utf8");
|
|
14088
|
+
this.child.stdout.on("data", (chunk) => this.handleStdout(String(chunk)));
|
|
14089
|
+
this.child.stderr.on("data", () => void 0);
|
|
14090
|
+
this.child.on("exit", () => this.handleExit());
|
|
14091
|
+
this.child.on("error", (error) => this.handleExit(error));
|
|
14092
|
+
this.child.stdin.on("error", (error) => this.handleExit(error));
|
|
14093
|
+
this.ready = this.request("init", { iceServers: configuration.iceServers ?? [] }).then(() => void 0);
|
|
14094
|
+
}
|
|
14095
|
+
connectionState = "new";
|
|
14096
|
+
iceConnectionState = "new";
|
|
14097
|
+
iceGatheringState = "new";
|
|
14098
|
+
signalingState = "stable";
|
|
14099
|
+
onconnectionstatechange = null;
|
|
14100
|
+
oniceconnectionstatechange = null;
|
|
14101
|
+
onicegatheringstatechange = null;
|
|
14102
|
+
onicecandidate = null;
|
|
14103
|
+
ondatachannel = null;
|
|
14104
|
+
child;
|
|
14105
|
+
pending = /* @__PURE__ */ new Map();
|
|
14106
|
+
channels = /* @__PURE__ */ new Map();
|
|
14107
|
+
nextRequestId = 1;
|
|
14108
|
+
nextChannelId = 1;
|
|
14109
|
+
stdout = "";
|
|
14110
|
+
closed = false;
|
|
14111
|
+
ready;
|
|
14112
|
+
createDataChannel(label, options) {
|
|
14113
|
+
const channelId = this.nextChannelId++;
|
|
14114
|
+
const channel = new ExternalNativeDataChannel(this, channelId, label, options?.ordered ?? true);
|
|
14115
|
+
this.channels.set(channelId, channel);
|
|
14116
|
+
this.notify("createDataChannel", { channelId, label, options: { ordered: options?.ordered ?? true } });
|
|
14117
|
+
return channel;
|
|
14118
|
+
}
|
|
14119
|
+
async createOffer() {
|
|
14120
|
+
await this.ready;
|
|
14121
|
+
return this.request("createOffer", {});
|
|
14122
|
+
}
|
|
14123
|
+
async createAnswer() {
|
|
14124
|
+
await this.ready;
|
|
14125
|
+
return this.request("createAnswer", {});
|
|
14126
|
+
}
|
|
14127
|
+
async setLocalDescription(description) {
|
|
14128
|
+
await this.ready;
|
|
14129
|
+
await this.request("setLocalDescription", { description });
|
|
14130
|
+
}
|
|
14131
|
+
async setRemoteDescription(description) {
|
|
14132
|
+
await this.ready;
|
|
14133
|
+
await this.request("setRemoteDescription", { description });
|
|
14134
|
+
}
|
|
14135
|
+
async addIceCandidate(candidate) {
|
|
14136
|
+
await this.ready;
|
|
14137
|
+
await this.request("addIceCandidate", { candidate: candidate ?? null });
|
|
14138
|
+
}
|
|
14139
|
+
async getStats() {
|
|
14140
|
+
await this.ready;
|
|
14141
|
+
return this.request("getStats", {});
|
|
14142
|
+
}
|
|
14143
|
+
close() {
|
|
14144
|
+
if (this.closed) return;
|
|
14145
|
+
this.closed = true;
|
|
14146
|
+
this.notify("close", {});
|
|
14147
|
+
const timer = setTimeout(() => {
|
|
14148
|
+
if (this.child.exitCode === null && this.child.signalCode === null) this.child.kill();
|
|
14149
|
+
}, 1e3);
|
|
14150
|
+
timer.unref?.();
|
|
14151
|
+
}
|
|
14152
|
+
sendChannelData(channelId, data) {
|
|
14153
|
+
const payload = typeof data === "string" ? { channelId, text: data } : { channelId, base64: Buffer.from(data).toString("base64") };
|
|
14154
|
+
this.notify("channelSend", payload);
|
|
14155
|
+
}
|
|
14156
|
+
closeChannel(channelId) {
|
|
14157
|
+
this.notify("channelClose", { channelId });
|
|
14158
|
+
}
|
|
14159
|
+
request(method, payload) {
|
|
14160
|
+
if (this.closed) return Promise.reject(new Error("native rtc helper is closed"));
|
|
14161
|
+
const id = this.nextRequestId++;
|
|
14162
|
+
const promise = new Promise((resolve2, reject) => {
|
|
14163
|
+
this.pending.set(id, { resolve: resolve2, reject });
|
|
14164
|
+
});
|
|
14165
|
+
this.write({ id, method, payload });
|
|
14166
|
+
return promise;
|
|
14167
|
+
}
|
|
14168
|
+
notify(method, payload) {
|
|
14169
|
+
if (this.closed && method !== "close") return;
|
|
14170
|
+
this.write({ method, payload });
|
|
14171
|
+
}
|
|
14172
|
+
write(message) {
|
|
14173
|
+
try {
|
|
14174
|
+
this.child.stdin.write(`${JSON.stringify(message)}
|
|
14175
|
+
`);
|
|
14176
|
+
} catch {
|
|
14177
|
+
this.handleExit(new Error("native rtc helper stdin is closed"));
|
|
14178
|
+
}
|
|
14179
|
+
}
|
|
14180
|
+
handleStdout(chunk) {
|
|
14181
|
+
this.stdout += chunk;
|
|
14182
|
+
for (; ; ) {
|
|
14183
|
+
const newline = this.stdout.indexOf("\n");
|
|
14184
|
+
if (newline < 0) return;
|
|
14185
|
+
const line = this.stdout.slice(0, newline);
|
|
14186
|
+
this.stdout = this.stdout.slice(newline + 1);
|
|
14187
|
+
if (line.trim() === "") continue;
|
|
14188
|
+
try {
|
|
14189
|
+
this.handleMessage(JSON.parse(line));
|
|
14190
|
+
} catch {
|
|
14191
|
+
this.handleExit(new Error("native rtc helper sent invalid JSON"));
|
|
14192
|
+
}
|
|
14193
|
+
}
|
|
14194
|
+
}
|
|
14195
|
+
handleMessage(message) {
|
|
14196
|
+
if ("id" in message) {
|
|
14197
|
+
const pending = this.pending.get(message.id);
|
|
14198
|
+
if (pending === void 0) return;
|
|
14199
|
+
this.pending.delete(message.id);
|
|
14200
|
+
if (message.ok) pending.resolve(message.value);
|
|
14201
|
+
else pending.reject(new Error(message.error ?? "native rtc helper command failed"));
|
|
14202
|
+
return;
|
|
14203
|
+
}
|
|
14204
|
+
if (message.event === "state") {
|
|
14205
|
+
this.connectionState = message.connectionState;
|
|
14206
|
+
this.iceConnectionState = message.iceConnectionState;
|
|
14207
|
+
this.iceGatheringState = message.iceGatheringState;
|
|
14208
|
+
this.signalingState = message.signalingState;
|
|
14209
|
+
this.onconnectionstatechange?.();
|
|
14210
|
+
this.oniceconnectionstatechange?.();
|
|
14211
|
+
this.onicegatheringstatechange?.();
|
|
14212
|
+
return;
|
|
14213
|
+
}
|
|
14214
|
+
if (message.event === "icecandidate") {
|
|
14215
|
+
this.onicecandidate?.({ candidate: message.candidate });
|
|
14216
|
+
return;
|
|
14217
|
+
}
|
|
14218
|
+
if (message.event === "datachannel") {
|
|
14219
|
+
const channel = this.channelFor(message.channelId, message.label, message.ordered);
|
|
14220
|
+
this.ondatachannel?.({ channel });
|
|
14221
|
+
channel.updateState(message.readyState, message.bufferedAmount);
|
|
14222
|
+
return;
|
|
14223
|
+
}
|
|
14224
|
+
if (message.event === "channel-state") {
|
|
14225
|
+
this.channels.get(message.channelId)?.updateState(message.readyState, message.bufferedAmount);
|
|
14226
|
+
return;
|
|
14227
|
+
}
|
|
14228
|
+
if (message.event === "channel-message") {
|
|
14229
|
+
const channel = this.channels.get(message.channelId);
|
|
14230
|
+
if (channel === void 0) return;
|
|
14231
|
+
if (message.text !== void 0) channel.emitMessage(message.text);
|
|
14232
|
+
else if (message.base64 !== void 0) channel.emitMessage(bufferToArrayBuffer(Buffer.from(message.base64, "base64")));
|
|
14233
|
+
return;
|
|
14234
|
+
}
|
|
14235
|
+
if (message.event === "channel-error") {
|
|
14236
|
+
this.channels.get(message.channelId)?.emitError();
|
|
14237
|
+
}
|
|
14238
|
+
}
|
|
14239
|
+
channelFor(channelId, label, ordered) {
|
|
14240
|
+
const existing = this.channels.get(channelId);
|
|
14241
|
+
if (existing !== void 0) return existing;
|
|
14242
|
+
const channel = new ExternalNativeDataChannel(this, channelId, label, ordered);
|
|
14243
|
+
this.channels.set(channelId, channel);
|
|
14244
|
+
return channel;
|
|
14245
|
+
}
|
|
14246
|
+
handleExit(error) {
|
|
14247
|
+
if (!this.closed) this.closed = true;
|
|
14248
|
+
const reason = error ?? new Error("native rtc helper exited");
|
|
14249
|
+
for (const pending of this.pending.values()) pending.reject(reason);
|
|
14250
|
+
this.pending.clear();
|
|
14251
|
+
for (const channel of this.channels.values()) channel.updateState("closed", 0);
|
|
14252
|
+
}
|
|
14253
|
+
};
|
|
14254
|
+
var ExternalNativeDataChannel = class {
|
|
14255
|
+
constructor(owner, channelId, label, ordered) {
|
|
14256
|
+
this.owner = owner;
|
|
14257
|
+
this.channelId = channelId;
|
|
14258
|
+
this.label = label;
|
|
14259
|
+
this.ordered = ordered;
|
|
14260
|
+
}
|
|
14261
|
+
readyState = "connecting";
|
|
14262
|
+
bufferedAmount = 0;
|
|
14263
|
+
binaryType = "arraybuffer";
|
|
14264
|
+
onopen = null;
|
|
14265
|
+
onmessage = null;
|
|
14266
|
+
onclose = null;
|
|
14267
|
+
onerror = null;
|
|
14268
|
+
onbufferedamountlow = null;
|
|
14269
|
+
send(data) {
|
|
14270
|
+
this.owner.sendChannelData(this.channelId, data);
|
|
14271
|
+
}
|
|
14272
|
+
close() {
|
|
14273
|
+
this.owner.closeChannel(this.channelId);
|
|
14274
|
+
}
|
|
14275
|
+
updateState(readyState, bufferedAmount) {
|
|
14276
|
+
const previous = this.readyState;
|
|
14277
|
+
this.readyState = readyState;
|
|
14278
|
+
this.bufferedAmount = bufferedAmount;
|
|
14279
|
+
if (previous !== "open" && readyState === "open") this.onopen?.();
|
|
14280
|
+
if (previous !== "closed" && readyState === "closed") this.onclose?.();
|
|
14281
|
+
if (bufferedAmount === 0) this.onbufferedamountlow?.();
|
|
14282
|
+
}
|
|
14283
|
+
emitMessage(data) {
|
|
14284
|
+
this.onmessage?.({ data });
|
|
14285
|
+
}
|
|
14286
|
+
emitError() {
|
|
14287
|
+
this.onerror?.();
|
|
14288
|
+
}
|
|
14289
|
+
};
|
|
14290
|
+
function bufferToArrayBuffer(buffer) {
|
|
14291
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
14292
|
+
}
|
|
14293
|
+
var HELPER_SOURCE = String.raw`
|
|
14294
|
+
import { createRequire } from 'node:module'
|
|
14295
|
+
import { createInterface } from 'node:readline'
|
|
14296
|
+
|
|
14297
|
+
const require = createRequire(process.env.DSH_REMOTE_RTC_HELPER_REQUIRE_FROM || (process.cwd() + '/package.json'))
|
|
14298
|
+
const wrtc = require('@roamhq/wrtc')
|
|
14299
|
+
const RTCPeerConnection = wrtc.RTCPeerConnection ?? wrtc.default?.RTCPeerConnection
|
|
14300
|
+
if (typeof RTCPeerConnection !== 'function') throw new Error('missing RTCPeerConnection')
|
|
14301
|
+
|
|
14302
|
+
let pc
|
|
14303
|
+
let nextRemoteChannelId = 100000
|
|
14304
|
+
const channels = new Map()
|
|
14305
|
+
let chain = Promise.resolve()
|
|
14306
|
+
|
|
14307
|
+
function send(message) {
|
|
14308
|
+
process.stdout.write(JSON.stringify(message) + '\n')
|
|
14309
|
+
}
|
|
14310
|
+
|
|
14311
|
+
function respond(id, ok, value, error) {
|
|
14312
|
+
if (id === undefined) return
|
|
14313
|
+
send(ok ? { id, ok: true, value } : { id, ok: false, error: String(error?.message ?? error ?? 'command failed') })
|
|
14314
|
+
}
|
|
14315
|
+
|
|
14316
|
+
function state() {
|
|
14317
|
+
return {
|
|
14318
|
+
event: 'state',
|
|
14319
|
+
connectionState: pc?.connectionState ?? 'closed',
|
|
14320
|
+
iceConnectionState: pc?.iceConnectionState ?? 'closed',
|
|
14321
|
+
iceGatheringState: pc?.iceGatheringState ?? 'complete',
|
|
14322
|
+
signalingState: pc?.signalingState ?? 'closed',
|
|
14323
|
+
}
|
|
14324
|
+
}
|
|
14325
|
+
|
|
14326
|
+
function emitState() {
|
|
14327
|
+
send(state())
|
|
14328
|
+
}
|
|
14329
|
+
|
|
14330
|
+
function normalizeCandidate(candidate) {
|
|
14331
|
+
if (candidate === null || candidate === undefined) return null
|
|
14332
|
+
if (typeof candidate.toJSON === 'function') return candidate.toJSON()
|
|
14333
|
+
return {
|
|
14334
|
+
candidate: candidate.candidate,
|
|
14335
|
+
sdpMid: candidate.sdpMid,
|
|
14336
|
+
sdpMLineIndex: candidate.sdpMLineIndex,
|
|
14337
|
+
usernameFragment: candidate.usernameFragment,
|
|
14338
|
+
}
|
|
14339
|
+
}
|
|
14340
|
+
|
|
14341
|
+
function attachChannel(channelId, channel) {
|
|
14342
|
+
channels.set(channelId, channel)
|
|
14343
|
+
try { channel.binaryType = 'arraybuffer' } catch {}
|
|
14344
|
+
const emitChannel = () => send({
|
|
14345
|
+
event: 'channel-state',
|
|
14346
|
+
channelId,
|
|
14347
|
+
readyState: channel.readyState,
|
|
14348
|
+
bufferedAmount: channel.bufferedAmount ?? 0,
|
|
14349
|
+
})
|
|
14350
|
+
channel.onopen = emitChannel
|
|
14351
|
+
channel.onclose = emitChannel
|
|
14352
|
+
channel.onerror = () => send({ event: 'channel-error', channelId })
|
|
14353
|
+
channel.onmessage = event => {
|
|
14354
|
+
const data = event.data
|
|
14355
|
+
if (typeof data === 'string') {
|
|
14356
|
+
send({ event: 'channel-message', channelId, text: data })
|
|
14357
|
+
return
|
|
14358
|
+
}
|
|
14359
|
+
send({ event: 'channel-message', channelId, base64: Buffer.from(data).toString('base64') })
|
|
14360
|
+
}
|
|
14361
|
+
send({
|
|
14362
|
+
event: 'datachannel',
|
|
14363
|
+
channelId,
|
|
14364
|
+
label: channel.label,
|
|
14365
|
+
ordered: channel.ordered,
|
|
14366
|
+
readyState: channel.readyState,
|
|
14367
|
+
bufferedAmount: channel.bufferedAmount ?? 0,
|
|
14368
|
+
})
|
|
14369
|
+
}
|
|
14370
|
+
|
|
14371
|
+
function serializeStats(stats) {
|
|
14372
|
+
const entries = []
|
|
14373
|
+
const push = (key, value) => {
|
|
14374
|
+
const copy = {}
|
|
14375
|
+
for (const [entryKey, entryValue] of Object.entries(value ?? {})) {
|
|
14376
|
+
if (typeof entryValue !== 'function') copy[entryKey] = entryValue
|
|
14377
|
+
}
|
|
14378
|
+
entries.push([String(key), copy])
|
|
14379
|
+
}
|
|
14380
|
+
if (typeof stats?.forEach === 'function') stats.forEach((value, key) => push(key, value))
|
|
14381
|
+
else for (const [key, value] of stats ?? []) push(key, value)
|
|
14382
|
+
return entries
|
|
14383
|
+
}
|
|
14384
|
+
|
|
14385
|
+
async function handle(command) {
|
|
14386
|
+
const { id, method, payload = {} } = command
|
|
14387
|
+
try {
|
|
14388
|
+
if (method === 'init') {
|
|
14389
|
+
pc = new RTCPeerConnection({ iceServers: payload.iceServers ?? [] })
|
|
14390
|
+
pc.onconnectionstatechange = emitState
|
|
14391
|
+
pc.oniceconnectionstatechange = emitState
|
|
14392
|
+
pc.onicegatheringstatechange = emitState
|
|
14393
|
+
pc.onsignalingstatechange = emitState
|
|
14394
|
+
pc.onicecandidate = event => send({ event: 'icecandidate', candidate: normalizeCandidate(event.candidate) })
|
|
14395
|
+
pc.ondatachannel = event => attachChannel(nextRemoteChannelId++, event.channel)
|
|
14396
|
+
emitState()
|
|
14397
|
+
respond(id, true, {})
|
|
14398
|
+
return
|
|
14399
|
+
}
|
|
14400
|
+
if (method === 'createDataChannel') {
|
|
14401
|
+
const channel = pc.createDataChannel(payload.label, payload.options)
|
|
14402
|
+
attachChannel(payload.channelId, channel)
|
|
14403
|
+
respond(id, true, {})
|
|
14404
|
+
return
|
|
14405
|
+
}
|
|
14406
|
+
if (method === 'createOffer') {
|
|
14407
|
+
respond(id, true, await pc.createOffer())
|
|
14408
|
+
return
|
|
14409
|
+
}
|
|
14410
|
+
if (method === 'createAnswer') {
|
|
14411
|
+
respond(id, true, await pc.createAnswer())
|
|
14412
|
+
return
|
|
14413
|
+
}
|
|
14414
|
+
if (method === 'setLocalDescription') {
|
|
14415
|
+
await pc.setLocalDescription(payload.description)
|
|
14416
|
+
emitState()
|
|
14417
|
+
respond(id, true, {})
|
|
14418
|
+
return
|
|
14419
|
+
}
|
|
14420
|
+
if (method === 'setRemoteDescription') {
|
|
14421
|
+
await pc.setRemoteDescription(payload.description)
|
|
14422
|
+
emitState()
|
|
14423
|
+
respond(id, true, {})
|
|
14424
|
+
return
|
|
14425
|
+
}
|
|
14426
|
+
if (method === 'addIceCandidate') {
|
|
14427
|
+
await pc.addIceCandidate(payload.candidate)
|
|
14428
|
+
respond(id, true, {})
|
|
14429
|
+
return
|
|
14430
|
+
}
|
|
14431
|
+
if (method === 'getStats') {
|
|
14432
|
+
respond(id, true, serializeStats(await pc.getStats()))
|
|
14433
|
+
return
|
|
14434
|
+
}
|
|
14435
|
+
if (method === 'channelSend') {
|
|
14436
|
+
const channel = channels.get(payload.channelId)
|
|
14437
|
+
if (channel === undefined) throw new Error('unknown data channel')
|
|
14438
|
+
channel.send(payload.text ?? Buffer.from(payload.base64, 'base64'))
|
|
14439
|
+
respond(id, true, {})
|
|
14440
|
+
return
|
|
14441
|
+
}
|
|
14442
|
+
if (method === 'channelClose') {
|
|
14443
|
+
channels.get(payload.channelId)?.close()
|
|
14444
|
+
respond(id, true, {})
|
|
14445
|
+
return
|
|
14446
|
+
}
|
|
14447
|
+
if (method === 'close') {
|
|
14448
|
+
for (const channel of channels.values()) {
|
|
14449
|
+
try { channel.close() } catch {}
|
|
14450
|
+
}
|
|
14451
|
+
channels.clear()
|
|
14452
|
+
try { pc?.close() } catch {}
|
|
14453
|
+
respond(id, true, {})
|
|
14454
|
+
setTimeout(() => process.exit(0), 0)
|
|
14455
|
+
return
|
|
14456
|
+
}
|
|
14457
|
+
throw new Error('unknown method ' + method)
|
|
14458
|
+
} catch (error) {
|
|
14459
|
+
respond(id, false, undefined, error)
|
|
14460
|
+
}
|
|
14461
|
+
}
|
|
14462
|
+
|
|
14463
|
+
const rl = createInterface({ input: process.stdin })
|
|
14464
|
+
rl.on('line', line => {
|
|
14465
|
+
chain = chain.then(() => handle(JSON.parse(line))).catch(error => {
|
|
14466
|
+
send({ id: -1, ok: false, error: String(error?.message ?? error) })
|
|
14467
|
+
})
|
|
14468
|
+
})
|
|
14469
|
+
process.stdin.on('end', () => {
|
|
14470
|
+
try { pc?.close() } catch {}
|
|
14471
|
+
process.exit(0)
|
|
14472
|
+
})
|
|
14473
|
+
`;
|
|
14474
|
+
|
|
14475
|
+
// src/werift-rtc.ts
|
|
13655
14476
|
var cachedFactory;
|
|
13656
|
-
|
|
13657
|
-
|
|
14477
|
+
var cachedWerift;
|
|
14478
|
+
var cachedNativeFactory;
|
|
14479
|
+
async function loadNodeRtcFactory(options = {}) {
|
|
14480
|
+
const nativeFactory = isElectronRuntime() ? await loadExternalNativeRtcFactory().catch(() => void 0) : await loadNativeRtcFactory().catch(() => void 0);
|
|
14481
|
+
return nativeFactory ?? await loadWeriftFactory(options);
|
|
14482
|
+
}
|
|
14483
|
+
async function loadWeriftFactory(options = {}) {
|
|
14484
|
+
const cacheable = isCacheableFactoryOptions(options);
|
|
14485
|
+
if (cacheable && cachedFactory !== void 0) return cachedFactory;
|
|
13658
14486
|
try {
|
|
13659
|
-
const werift = await import("werift");
|
|
13660
|
-
|
|
13661
|
-
|
|
14487
|
+
const werift = cachedWerift ?? await import("werift");
|
|
14488
|
+
cachedWerift = werift;
|
|
14489
|
+
const routeCandidates = options.preferredHostIpv4Candidates ?? await detectRouteHostIpv4Candidates(options.routeTargets ?? [], options.routeProbeTimeoutMs);
|
|
14490
|
+
const factory = buildWeriftFactory(werift, { ...options, preferredHostIpv4Candidates: routeCandidates });
|
|
14491
|
+
if (cacheable) cachedFactory = factory;
|
|
14492
|
+
return factory;
|
|
13662
14493
|
} catch {
|
|
13663
14494
|
return void 0;
|
|
13664
14495
|
}
|
|
13665
14496
|
}
|
|
13666
|
-
function
|
|
14497
|
+
async function loadNativeRtcFactory() {
|
|
14498
|
+
if (cachedNativeFactory !== void 0) return cachedNativeFactory;
|
|
14499
|
+
try {
|
|
14500
|
+
const wrtc = await import("@roamhq/wrtc");
|
|
14501
|
+
const RTCPeerConnection2 = wrtc.RTCPeerConnection ?? wrtc.default?.RTCPeerConnection;
|
|
14502
|
+
if (RTCPeerConnection2 === void 0) return void 0;
|
|
14503
|
+
cachedNativeFactory = buildNativeRtcFactory(RTCPeerConnection2);
|
|
14504
|
+
return cachedNativeFactory;
|
|
14505
|
+
} catch {
|
|
14506
|
+
return void 0;
|
|
14507
|
+
}
|
|
14508
|
+
}
|
|
14509
|
+
function isElectronRuntime() {
|
|
14510
|
+
const versions = process.versions;
|
|
14511
|
+
return typeof versions.electron === "string" || process.env.ELECTRON_RUN_AS_NODE === "1";
|
|
14512
|
+
}
|
|
14513
|
+
function buildNativeRtcFactory(RTCPeerConnection2) {
|
|
14514
|
+
return {
|
|
14515
|
+
create(configuration) {
|
|
14516
|
+
const raw = new RTCPeerConnection2({ iceServers: configuration.iceServers });
|
|
14517
|
+
return {
|
|
14518
|
+
get connectionState() {
|
|
14519
|
+
return raw.connectionState;
|
|
14520
|
+
},
|
|
14521
|
+
get iceConnectionState() {
|
|
14522
|
+
return raw.iceConnectionState;
|
|
14523
|
+
},
|
|
14524
|
+
get iceGatheringState() {
|
|
14525
|
+
return raw.iceGatheringState;
|
|
14526
|
+
},
|
|
14527
|
+
get signalingState() {
|
|
14528
|
+
return raw.signalingState;
|
|
14529
|
+
},
|
|
14530
|
+
set onconnectionstatechange(value) {
|
|
14531
|
+
raw.onconnectionstatechange = value;
|
|
14532
|
+
},
|
|
14533
|
+
get onconnectionstatechange() {
|
|
14534
|
+
return raw.onconnectionstatechange;
|
|
14535
|
+
},
|
|
14536
|
+
set oniceconnectionstatechange(value) {
|
|
14537
|
+
raw.oniceconnectionstatechange = value;
|
|
14538
|
+
},
|
|
14539
|
+
get oniceconnectionstatechange() {
|
|
14540
|
+
return raw.oniceconnectionstatechange;
|
|
14541
|
+
},
|
|
14542
|
+
set onicegatheringstatechange(value) {
|
|
14543
|
+
raw.onicegatheringstatechange = value;
|
|
14544
|
+
},
|
|
14545
|
+
get onicegatheringstatechange() {
|
|
14546
|
+
return raw.onicegatheringstatechange;
|
|
14547
|
+
},
|
|
14548
|
+
set onicecandidate(value) {
|
|
14549
|
+
raw.onicecandidate = value === null ? null : (event) => value({
|
|
14550
|
+
candidate: event.candidate === null ? null : normalizeNativeCandidate(event.candidate)
|
|
14551
|
+
});
|
|
14552
|
+
},
|
|
14553
|
+
get onicecandidate() {
|
|
14554
|
+
return raw.onicecandidate;
|
|
14555
|
+
},
|
|
14556
|
+
set ondatachannel(value) {
|
|
14557
|
+
raw.ondatachannel = value === null ? null : (event) => value({ channel: adaptNativeDataChannel(event.channel) });
|
|
14558
|
+
},
|
|
14559
|
+
get ondatachannel() {
|
|
14560
|
+
return raw.ondatachannel;
|
|
14561
|
+
},
|
|
14562
|
+
createDataChannel(label, options) {
|
|
14563
|
+
return adaptNativeDataChannel(raw.createDataChannel(label, options));
|
|
14564
|
+
},
|
|
14565
|
+
createOffer() {
|
|
14566
|
+
return raw.createOffer();
|
|
14567
|
+
},
|
|
14568
|
+
createAnswer() {
|
|
14569
|
+
return raw.createAnswer();
|
|
14570
|
+
},
|
|
14571
|
+
setLocalDescription(description) {
|
|
14572
|
+
return raw.setLocalDescription(description);
|
|
14573
|
+
},
|
|
14574
|
+
setRemoteDescription(description) {
|
|
14575
|
+
return raw.setRemoteDescription(description);
|
|
14576
|
+
},
|
|
14577
|
+
addIceCandidate(candidate) {
|
|
14578
|
+
return raw.addIceCandidate(candidate);
|
|
14579
|
+
},
|
|
14580
|
+
getStats() {
|
|
14581
|
+
return raw.getStats();
|
|
14582
|
+
},
|
|
14583
|
+
close() {
|
|
14584
|
+
raw.close();
|
|
14585
|
+
}
|
|
14586
|
+
};
|
|
14587
|
+
}
|
|
14588
|
+
};
|
|
14589
|
+
}
|
|
14590
|
+
function buildWeriftFactory(werift, options = {}) {
|
|
13667
14591
|
return {
|
|
13668
14592
|
create(configuration) {
|
|
13669
|
-
const
|
|
14593
|
+
const hostIpv4Candidates = detectHostIpv4Candidates(
|
|
14594
|
+
options.interfaces ?? networkInterfaces(),
|
|
14595
|
+
options.preferredHostIpv4Candidates
|
|
14596
|
+
);
|
|
14597
|
+
const allowedHostIpv4 = new Set(hostIpv4Candidates);
|
|
13670
14598
|
const raw = new werift.RTCPeerConnection({
|
|
13671
|
-
iceServers: configuration.iceServers ?? [],
|
|
14599
|
+
iceServers: orderIceServersForWerift(configuration.iceServers ?? []),
|
|
14600
|
+
iceUseIpv4: true,
|
|
13672
14601
|
iceUseIpv6: false,
|
|
13673
14602
|
iceUseLinkLocalAddress: false,
|
|
13674
|
-
...
|
|
14603
|
+
...hostIpv4Candidates.length === 0 ? {} : {
|
|
14604
|
+
iceAdditionalHostAddresses: hostIpv4Candidates,
|
|
14605
|
+
iceFilterCandidatePair: (pair) => {
|
|
14606
|
+
const allowed = shouldUseCandidatePair(pair, allowedHostIpv4);
|
|
14607
|
+
if (!allowed) {
|
|
14608
|
+
configuration.onDiagnostic?.({
|
|
14609
|
+
type: "candidate-pair-filtered",
|
|
14610
|
+
localCandidate: summarizeWeriftCandidate(localCandidate(pair)),
|
|
14611
|
+
reason: "local-host-not-allowed"
|
|
14612
|
+
});
|
|
14613
|
+
}
|
|
14614
|
+
return allowed;
|
|
14615
|
+
}
|
|
14616
|
+
}
|
|
13675
14617
|
});
|
|
13676
14618
|
let onIceCandidate = null;
|
|
13677
14619
|
let onDataChannel = null;
|
|
13678
14620
|
raw.onicecandidate = (event) => {
|
|
13679
14621
|
if (onIceCandidate === null) return;
|
|
13680
|
-
|
|
14622
|
+
const candidate = event.candidate === void 0 ? null : event.candidate.toJSON();
|
|
14623
|
+
if (candidate !== null && !shouldAdvertiseCandidate(candidate, allowedHostIpv4)) {
|
|
14624
|
+
configuration.onDiagnostic?.({
|
|
14625
|
+
type: "local-candidate-filtered",
|
|
14626
|
+
candidate: summarizeIceCandidate(candidate),
|
|
14627
|
+
reason: "local-host-not-allowed"
|
|
14628
|
+
});
|
|
14629
|
+
return;
|
|
14630
|
+
}
|
|
14631
|
+
onIceCandidate({ candidate });
|
|
13681
14632
|
};
|
|
13682
14633
|
raw.ondatachannel = (event) => {
|
|
13683
14634
|
if (onDataChannel === null) return;
|
|
@@ -13726,8 +14677,8 @@ function buildWeriftFactory(werift) {
|
|
|
13726
14677
|
get ondatachannel() {
|
|
13727
14678
|
return onDataChannel;
|
|
13728
14679
|
},
|
|
13729
|
-
createDataChannel(label,
|
|
13730
|
-
return adaptDataChannel2(raw.createDataChannel(label, { ordered:
|
|
14680
|
+
createDataChannel(label, options2) {
|
|
14681
|
+
return adaptDataChannel2(raw.createDataChannel(label, { ordered: options2?.ordered ?? true }));
|
|
13731
14682
|
},
|
|
13732
14683
|
createOffer() {
|
|
13733
14684
|
return raw.createOffer();
|
|
@@ -13809,27 +14760,245 @@ function adaptDataChannel2(raw) {
|
|
|
13809
14760
|
}
|
|
13810
14761
|
};
|
|
13811
14762
|
}
|
|
14763
|
+
function adaptNativeDataChannel(raw) {
|
|
14764
|
+
let onmessage = null;
|
|
14765
|
+
return {
|
|
14766
|
+
get label() {
|
|
14767
|
+
return raw.label;
|
|
14768
|
+
},
|
|
14769
|
+
get ordered() {
|
|
14770
|
+
return raw.ordered;
|
|
14771
|
+
},
|
|
14772
|
+
get readyState() {
|
|
14773
|
+
return raw.readyState;
|
|
14774
|
+
},
|
|
14775
|
+
get bufferedAmount() {
|
|
14776
|
+
return raw.bufferedAmount;
|
|
14777
|
+
},
|
|
14778
|
+
get binaryType() {
|
|
14779
|
+
return raw.binaryType;
|
|
14780
|
+
},
|
|
14781
|
+
set binaryType(value) {
|
|
14782
|
+
raw.binaryType = value;
|
|
14783
|
+
},
|
|
14784
|
+
set onopen(value) {
|
|
14785
|
+
raw.onopen = value;
|
|
14786
|
+
},
|
|
14787
|
+
get onopen() {
|
|
14788
|
+
return raw.onopen;
|
|
14789
|
+
},
|
|
14790
|
+
set onmessage(value) {
|
|
14791
|
+
onmessage = value;
|
|
14792
|
+
raw.onmessage = value === null ? null : (event) => value({ data: normalizeNativeMessageData(event.data) });
|
|
14793
|
+
},
|
|
14794
|
+
get onmessage() {
|
|
14795
|
+
return onmessage;
|
|
14796
|
+
},
|
|
14797
|
+
set onclose(value) {
|
|
14798
|
+
raw.onclose = value;
|
|
14799
|
+
},
|
|
14800
|
+
get onclose() {
|
|
14801
|
+
return raw.onclose;
|
|
14802
|
+
},
|
|
14803
|
+
set onerror(value) {
|
|
14804
|
+
raw.onerror = value;
|
|
14805
|
+
},
|
|
14806
|
+
get onerror() {
|
|
14807
|
+
return raw.onerror;
|
|
14808
|
+
},
|
|
14809
|
+
set onbufferedamountlow(value) {
|
|
14810
|
+
raw.onbufferedamountlow = value;
|
|
14811
|
+
},
|
|
14812
|
+
get onbufferedamountlow() {
|
|
14813
|
+
return raw.onbufferedamountlow;
|
|
14814
|
+
},
|
|
14815
|
+
send(data) {
|
|
14816
|
+
raw.send(typeof data === "string" ? data : Buffer.from(data));
|
|
14817
|
+
},
|
|
14818
|
+
close() {
|
|
14819
|
+
raw.close();
|
|
14820
|
+
}
|
|
14821
|
+
};
|
|
14822
|
+
}
|
|
13812
14823
|
function toArrayBuffer2(data) {
|
|
13813
14824
|
if (typeof data === "string") return data;
|
|
13814
14825
|
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
13815
14826
|
}
|
|
13816
|
-
function
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
14827
|
+
function normalizeNativeCandidate(candidate) {
|
|
14828
|
+
if (typeof candidate.toJSON === "function") return candidate.toJSON();
|
|
14829
|
+
return {
|
|
14830
|
+
candidate: candidate.candidate,
|
|
14831
|
+
sdpMid: candidate.sdpMid,
|
|
14832
|
+
sdpMLineIndex: candidate.sdpMLineIndex,
|
|
14833
|
+
usernameFragment: candidate.usernameFragment
|
|
14834
|
+
};
|
|
14835
|
+
}
|
|
14836
|
+
function normalizeNativeMessageData(data) {
|
|
14837
|
+
if (typeof data === "string") return data;
|
|
14838
|
+
if (data instanceof ArrayBuffer) return data;
|
|
14839
|
+
if (ArrayBuffer.isView(data)) {
|
|
14840
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
14841
|
+
}
|
|
14842
|
+
return new Uint8Array().buffer;
|
|
14843
|
+
}
|
|
14844
|
+
function detectHostIpv4Candidates(interfaces, preferredHostIpv4Candidates = []) {
|
|
14845
|
+
const candidates = [];
|
|
14846
|
+
const routePreference = new Map(preferredHostIpv4Candidates.map((ip, index) => [ip, 1e4 - index]));
|
|
14847
|
+
for (const [name2, addresses] of Object.entries(interfaces)) {
|
|
14848
|
+
const interfaceScore = physicalInterfaceScore(name2);
|
|
14849
|
+
if (interfaceScore === void 0) continue;
|
|
13821
14850
|
for (const address of addresses ?? []) {
|
|
13822
|
-
if (address.internal || address.family
|
|
14851
|
+
if (address.internal || !isIpv4Family(address.family)) continue;
|
|
13823
14852
|
const ip = address.address;
|
|
13824
|
-
if (
|
|
13825
|
-
|
|
13826
|
-
|
|
14853
|
+
if (!isUsableIpv4(ip)) continue;
|
|
14854
|
+
candidates.push({
|
|
14855
|
+
name: name2,
|
|
14856
|
+
ip,
|
|
14857
|
+
score: interfaceScore + (isPrivate(ip) ? 1e3 : 0) + (routePreference.get(ip) ?? 0)
|
|
14858
|
+
});
|
|
13827
14859
|
}
|
|
13828
14860
|
}
|
|
13829
|
-
|
|
14861
|
+
candidates.sort((left, right) => {
|
|
14862
|
+
const score = right.score - left.score;
|
|
14863
|
+
if (score !== 0) return score;
|
|
14864
|
+
const name2 = left.name.localeCompare(right.name, "en");
|
|
14865
|
+
return name2 === 0 ? left.ip.localeCompare(right.ip, "en", { numeric: true }) : name2;
|
|
14866
|
+
});
|
|
14867
|
+
return [...new Set(candidates.map((candidate) => candidate.ip))];
|
|
14868
|
+
}
|
|
14869
|
+
async function detectRouteHostIpv4Candidates(targets, timeoutMs = 500) {
|
|
14870
|
+
const detected = await Promise.all(targets.map(async (target) => {
|
|
14871
|
+
const routeTarget = parseRouteTarget(target);
|
|
14872
|
+
return routeTarget === void 0 ? void 0 : await detectRouteHostIpv4(routeTarget, timeoutMs).catch(() => void 0);
|
|
14873
|
+
}));
|
|
14874
|
+
return [...new Set(detected.filter((ip) => ip !== void 0 && isUsableIpv4(ip)))];
|
|
14875
|
+
}
|
|
14876
|
+
function shouldUseCandidatePair(pair, allowedHostIpv4) {
|
|
14877
|
+
if (allowedHostIpv4.size === 0) return true;
|
|
14878
|
+
const candidate = localCandidate(pair);
|
|
14879
|
+
if (candidate === void 0) return true;
|
|
14880
|
+
return shouldUseLocalCandidate(candidate, allowedHostIpv4);
|
|
14881
|
+
}
|
|
14882
|
+
function shouldAdvertiseCandidate(candidate, allowedHostIpv4) {
|
|
14883
|
+
if (allowedHostIpv4.size === 0) return true;
|
|
14884
|
+
const parsed = parseIceCandidate(candidate);
|
|
14885
|
+
if (parsed === void 0) return true;
|
|
14886
|
+
return shouldUseLocalCandidate(parsed, allowedHostIpv4);
|
|
14887
|
+
}
|
|
14888
|
+
function shouldUseLocalCandidate(candidate, allowedHostIpv4) {
|
|
14889
|
+
if (candidate.type === "host") return candidate.host !== void 0 && allowedHostIpv4.has(candidate.host);
|
|
14890
|
+
if (candidate.type === "srflx" && candidate.relatedAddress !== void 0) {
|
|
14891
|
+
return allowedHostIpv4.has(candidate.relatedAddress);
|
|
14892
|
+
}
|
|
14893
|
+
return true;
|
|
14894
|
+
}
|
|
14895
|
+
function localCandidate(pair) {
|
|
14896
|
+
try {
|
|
14897
|
+
return pair.localCandidate;
|
|
14898
|
+
} catch {
|
|
14899
|
+
return void 0;
|
|
14900
|
+
}
|
|
14901
|
+
}
|
|
14902
|
+
function summarizeWeriftCandidate(candidate) {
|
|
14903
|
+
if (candidate === void 0) return void 0;
|
|
14904
|
+
const address = summarizeAddress(candidate.host);
|
|
14905
|
+
const related = summarizeAddress(candidate.relatedAddress);
|
|
14906
|
+
return {
|
|
14907
|
+
candidateType: candidate.type === "host" || candidate.type === "srflx" || candidate.type === "prflx" || candidate.type === "relay" ? candidate.type : "unknown",
|
|
14908
|
+
...address,
|
|
14909
|
+
...candidate.relatedAddress === void 0 ? {} : { relatedAddressFamily: related.addressFamily, relatedAddressScope: related.addressScope }
|
|
14910
|
+
};
|
|
14911
|
+
}
|
|
14912
|
+
function orderIceServersForWerift(iceServers) {
|
|
14913
|
+
return iceServers.map((server) => {
|
|
14914
|
+
const urls = Array.isArray(server.urls) ? [...server.urls] : [server.urls];
|
|
14915
|
+
urls.sort((left, right) => iceUrlScore(left) - iceUrlScore(right));
|
|
14916
|
+
return { ...server, urls: Array.isArray(server.urls) ? urls : urls[0] ?? server.urls };
|
|
14917
|
+
});
|
|
14918
|
+
}
|
|
14919
|
+
function iceUrlScore(url) {
|
|
14920
|
+
const value = url.trim().toLowerCase();
|
|
14921
|
+
if (value.startsWith("stun:") || value.startsWith("stuns:")) return 0;
|
|
14922
|
+
if (value.startsWith("turn:") && value.includes("transport=tcp")) return 10;
|
|
14923
|
+
if (value.startsWith("turns:") && value.includes("transport=tcp")) return 20;
|
|
14924
|
+
if (value.startsWith("turn:") && value.includes("transport=udp")) return 30;
|
|
14925
|
+
if (value.startsWith("turns:")) return 40;
|
|
14926
|
+
if (value.startsWith("turn:")) return 50;
|
|
14927
|
+
return 100;
|
|
14928
|
+
}
|
|
14929
|
+
function parseRouteTarget(value) {
|
|
14930
|
+
try {
|
|
14931
|
+
const url = new URL(value);
|
|
14932
|
+
const port = Number(url.port || (url.protocol === "http:" ? "80" : "443"));
|
|
14933
|
+
if (!Number.isSafeInteger(port) || port <= 0 || port > 65535 || url.hostname.length === 0) return void 0;
|
|
14934
|
+
return { host: url.hostname, port };
|
|
14935
|
+
} catch {
|
|
14936
|
+
return void 0;
|
|
14937
|
+
}
|
|
13830
14938
|
}
|
|
13831
|
-
function
|
|
13832
|
-
|
|
14939
|
+
async function detectRouteHostIpv4(target, timeoutMs) {
|
|
14940
|
+
const socket = createSocket("udp4");
|
|
14941
|
+
try {
|
|
14942
|
+
return await new Promise((resolve2, reject) => {
|
|
14943
|
+
const timer = setTimeout(() => {
|
|
14944
|
+
cleanup();
|
|
14945
|
+
resolve2(void 0);
|
|
14946
|
+
}, timeoutMs);
|
|
14947
|
+
const cleanup = () => {
|
|
14948
|
+
clearTimeout(timer);
|
|
14949
|
+
socket.off("error", onError);
|
|
14950
|
+
};
|
|
14951
|
+
const onError = (error) => {
|
|
14952
|
+
cleanup();
|
|
14953
|
+
reject(error);
|
|
14954
|
+
};
|
|
14955
|
+
socket.once("error", onError);
|
|
14956
|
+
socket.connect(target.port, target.host, () => {
|
|
14957
|
+
cleanup();
|
|
14958
|
+
const address = socket.address();
|
|
14959
|
+
resolve2(typeof address === "string" ? void 0 : address.address);
|
|
14960
|
+
});
|
|
14961
|
+
});
|
|
14962
|
+
} finally {
|
|
14963
|
+
socket.close();
|
|
14964
|
+
}
|
|
14965
|
+
}
|
|
14966
|
+
function isCacheableFactoryOptions(options) {
|
|
14967
|
+
return options.interfaces === void 0 && options.preferredHostIpv4Candidates === void 0 && options.routeTargets === void 0;
|
|
14968
|
+
}
|
|
14969
|
+
function parseIceCandidate(candidate) {
|
|
14970
|
+
const value = candidate.candidate?.trim();
|
|
14971
|
+
if (value === void 0 || value.length === 0) return void 0;
|
|
14972
|
+
const parts = value.replace(/^candidate:/, "").split(/\s+/);
|
|
14973
|
+
const typeIndex = parts.indexOf("typ");
|
|
14974
|
+
if (typeIndex < 0) return void 0;
|
|
14975
|
+
const host = parts[4];
|
|
14976
|
+
const type = parts[typeIndex + 1];
|
|
14977
|
+
const relatedAddressIndex = parts.indexOf("raddr");
|
|
14978
|
+
return {
|
|
14979
|
+
...host === void 0 ? {} : { host },
|
|
14980
|
+
...type === void 0 ? {} : { type },
|
|
14981
|
+
...relatedAddressIndex < 0 || parts[relatedAddressIndex + 1] === void 0 ? {} : { relatedAddress: parts[relatedAddressIndex + 1] }
|
|
14982
|
+
};
|
|
14983
|
+
}
|
|
14984
|
+
function physicalInterfaceScore(name2) {
|
|
14985
|
+
const value = name2.toLowerCase();
|
|
14986
|
+
if (/^(utun|tun|tap|ppp|bridge|awdl|llw|gif|stf|anpi|ap\d|vmnet|veth|docker|br-|vboxnet|lo)/.test(value)) {
|
|
14987
|
+
return void 0;
|
|
14988
|
+
}
|
|
14989
|
+
if (/^(eth|eno|ens|enp)/.test(value)) return 500;
|
|
14990
|
+
if (/^(en|wlan|wl|wifi|wi-fi)/.test(value)) return 450;
|
|
14991
|
+
return 300;
|
|
14992
|
+
}
|
|
14993
|
+
function isIpv4Family(family) {
|
|
14994
|
+
return family === "IPv4" || family === 4;
|
|
14995
|
+
}
|
|
14996
|
+
function isUsableIpv4(ip) {
|
|
14997
|
+
const parts = ip.split(".").map(Number);
|
|
14998
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
14999
|
+
if (parts[0] === 0 || parts[0] === 127 || parts[0] >= 224) return false;
|
|
15000
|
+
if (parts[0] === 169 && parts[1] === 254) return false;
|
|
15001
|
+
return !isCgnat(ip);
|
|
13833
15002
|
}
|
|
13834
15003
|
function isCgnat(ip) {
|
|
13835
15004
|
const parts = ip.split(".").map(Number);
|
|
@@ -13846,8 +15015,9 @@ function isPrivate(ip) {
|
|
|
13846
15015
|
// src/client-runtime.ts
|
|
13847
15016
|
var REMOTE_COMMAND_LIST_MIN_VERSION = [0, 3, 16];
|
|
13848
15017
|
var REMOTE_FILE_VIEWER_MIN_VERSION = [0, 3, 17];
|
|
15018
|
+
var DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 12e3;
|
|
13849
15019
|
var ClientModeRuntime = class {
|
|
13850
|
-
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider =
|
|
15020
|
+
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory) {
|
|
13851
15021
|
this.config = config;
|
|
13852
15022
|
this.identities = identities;
|
|
13853
15023
|
this.server = server;
|
|
@@ -14083,61 +15253,76 @@ var ClientModeRuntime = class {
|
|
|
14083
15253
|
const presence = await this.server.presenceFor(targetDeviceId);
|
|
14084
15254
|
if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
|
|
14085
15255
|
const credentials = await this.server.authenticate(identity);
|
|
14086
|
-
const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider().catch(() => void 0);
|
|
15256
|
+
const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => void 0);
|
|
14087
15257
|
let webRtcFallback = false;
|
|
14088
|
-
const createTransport = (
|
|
15258
|
+
const createTransport = (attempt) => new AdaptiveTransport(
|
|
14089
15259
|
websocketUrl(this.server.baseUrl),
|
|
14090
15260
|
{
|
|
14091
15261
|
role: "client",
|
|
14092
15262
|
deviceId: identity.deviceId,
|
|
14093
15263
|
accessToken: credentials.accessToken,
|
|
14094
15264
|
targetDeviceId,
|
|
14095
|
-
forceRelay: this.config.forceRelay ||
|
|
14096
|
-
preferredTransports:
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
15265
|
+
forceRelay: this.config.forceRelay || attempt === "relay",
|
|
15266
|
+
preferredTransports: preferredTransportsForAttempt(attempt),
|
|
15267
|
+
negotiateTimeoutMs: attempt === "direct" ? DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS : void 0,
|
|
15268
|
+
...rtcFactory === void 0 || attempt === "relay" ? {} : { rtcFactory },
|
|
15269
|
+
fetchIceServers: async (connectionId) => iceServersForAttempt(attempt, await this.server.turnCredentials(connectionId)),
|
|
15270
|
+
onWebRtcFallback: (error, diagnostics) => {
|
|
14100
15271
|
webRtcFallback = true;
|
|
14101
|
-
this.logger.warn("remote Harness WebRTC failed; using relay", {
|
|
15272
|
+
this.logger.warn(attempt === "direct" ? "remote Harness direct WebRTC failed; trying TURN" : "remote Harness TURN WebRTC failed; using relay", {
|
|
14102
15273
|
targetDeviceId: shortId(target.deviceId),
|
|
14103
|
-
|
|
15274
|
+
attempt,
|
|
15275
|
+
reason: diagnosticReason(error),
|
|
15276
|
+
...webrtcDiagnosticsLogFields(diagnostics)
|
|
14104
15277
|
});
|
|
14105
15278
|
}
|
|
14106
15279
|
}
|
|
14107
15280
|
);
|
|
14108
|
-
|
|
14109
|
-
let
|
|
15281
|
+
const attempts = this.config.forceRelay || rtcFactory === void 0 ? ["relay"] : ["direct", "turn", "relay"];
|
|
15282
|
+
let transport;
|
|
15283
|
+
let client;
|
|
14110
15284
|
try {
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
await client.close();
|
|
14115
|
-
transport = createTransport(true);
|
|
15285
|
+
for (const attempt of attempts) {
|
|
15286
|
+
webRtcFallback = false;
|
|
15287
|
+
transport = createTransport(attempt);
|
|
14116
15288
|
client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 6e4);
|
|
14117
15289
|
await client.connect();
|
|
14118
15290
|
signal?.throwIfAborted();
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
15291
|
+
if (attempt === "relay" || !webRtcFallback) break;
|
|
15292
|
+
await client.close();
|
|
15293
|
+
client = void 0;
|
|
15294
|
+
transport = void 0;
|
|
15295
|
+
if (attempt === "turn") {
|
|
15296
|
+
this.logger.info("remote Harness relay fallback re-established", {
|
|
15297
|
+
targetDeviceId: shortId(target.deviceId)
|
|
15298
|
+
});
|
|
15299
|
+
}
|
|
15300
|
+
}
|
|
15301
|
+
if (client === void 0 || transport === void 0) {
|
|
15302
|
+
throw new ClientModeError("CONNECTION_FAILED", "Unable to establish a remote transport.", true);
|
|
14122
15303
|
}
|
|
14123
|
-
|
|
14124
|
-
|
|
15304
|
+
const connectedClient = client;
|
|
15305
|
+
const connectedTransport = transport;
|
|
15306
|
+
connectedClient.onClose(() => {
|
|
15307
|
+
if (this.connected?.client !== connectedClient) return;
|
|
14125
15308
|
this.connected = void 0;
|
|
14126
15309
|
this.pendingWorkspaceSelection = void 0;
|
|
14127
15310
|
this.proxySwitch.selectLocal();
|
|
14128
15311
|
this.gatewaySwitch.selectLocal();
|
|
14129
|
-
void
|
|
15312
|
+
void connectedClient.close().catch(() => void 0);
|
|
14130
15313
|
this.logger.warn("remote Harness transport closed; falling back to local mode", {
|
|
14131
15314
|
targetDeviceId: shortId(target.deviceId)
|
|
14132
15315
|
});
|
|
14133
15316
|
});
|
|
15317
|
+
const connectionDetails = await connectedTransport.connectionDetails().catch(() => void 0);
|
|
14134
15318
|
this.logger.info("remote Harness transport ready", {
|
|
14135
15319
|
targetDeviceId: shortId(target.deviceId),
|
|
14136
|
-
transport:
|
|
15320
|
+
transport: connectedClient.getStats().mode,
|
|
15321
|
+
...webrtcDiagnosticsLogFields(connectionDetails?.webRtc?.diagnostics)
|
|
14137
15322
|
});
|
|
14138
|
-
return { client, target, transport, features: remoteHostFeatures(serverDevice.clientVersion) };
|
|
15323
|
+
return { client: connectedClient, target, transport: connectedTransport, features: remoteHostFeatures(serverDevice.clientVersion) };
|
|
14139
15324
|
} catch (error) {
|
|
14140
|
-
await client
|
|
15325
|
+
await client?.close().catch(() => void 0);
|
|
14141
15326
|
throw error;
|
|
14142
15327
|
}
|
|
14143
15328
|
}
|
|
@@ -14284,6 +15469,20 @@ function websocketUrl(baseUrl) {
|
|
|
14284
15469
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/ws/v1/connect`;
|
|
14285
15470
|
return url.toString();
|
|
14286
15471
|
}
|
|
15472
|
+
function webrtcDiagnosticsLogFields(diagnostics) {
|
|
15473
|
+
if (diagnostics === void 0) return {};
|
|
15474
|
+
return {
|
|
15475
|
+
rtcConnectionState: diagnostics.connectionState,
|
|
15476
|
+
rtcIceConnectionState: diagnostics.iceConnectionState,
|
|
15477
|
+
rtcIceGatheringState: diagnostics.iceGatheringState,
|
|
15478
|
+
rtcLocalCandidates: diagnostics.localCandidates,
|
|
15479
|
+
rtcRemoteCandidates: diagnostics.remoteCandidates,
|
|
15480
|
+
rtcCandidatePairs: diagnostics.candidatePairs,
|
|
15481
|
+
rtcFilteredLocalCandidates: diagnostics.filteredLocalCandidates,
|
|
15482
|
+
rtcFilteredCandidatePairs: diagnostics.filteredCandidatePairs,
|
|
15483
|
+
...diagnostics.selectedPath === void 0 ? {} : { rtcSelectedPath: diagnostics.selectedPath }
|
|
15484
|
+
};
|
|
15485
|
+
}
|
|
14287
15486
|
function record(value) {
|
|
14288
15487
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
14289
15488
|
throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
|
|
@@ -14361,6 +15560,14 @@ function diagnosticReason(error) {
|
|
|
14361
15560
|
const message = error.message.replace(/[\r\n\t]+/g, " ").slice(0, 240);
|
|
14362
15561
|
return code === void 0 ? message : `${code}: ${message}`;
|
|
14363
15562
|
}
|
|
15563
|
+
function preferredTransportsForAttempt(attempt) {
|
|
15564
|
+
if (attempt === "direct") return ["lan", "p2p", "relay"];
|
|
15565
|
+
if (attempt === "turn") return ["turn", "relay"];
|
|
15566
|
+
return ["relay"];
|
|
15567
|
+
}
|
|
15568
|
+
function iceServersForAttempt(attempt, iceServers) {
|
|
15569
|
+
return attempt === "direct" ? stunOnlyIceServers(iceServers) : iceServers;
|
|
15570
|
+
}
|
|
14364
15571
|
|
|
14365
15572
|
// src/control-runtime.ts
|
|
14366
15573
|
import { hostname as hostname2 } from "node:os";
|
|
@@ -14369,7 +15576,7 @@ import { hostname as hostname2 } from "node:os";
|
|
|
14369
15576
|
import { createHash } from "node:crypto";
|
|
14370
15577
|
import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
14371
15578
|
import { homedir } from "node:os";
|
|
14372
|
-
import { dirname, join } from "node:path";
|
|
15579
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
14373
15580
|
var identitySchema = external_exports.object({
|
|
14374
15581
|
schemaVersion: external_exports.literal(1),
|
|
14375
15582
|
deviceId: external_exports.string().uuid(),
|
|
@@ -14398,14 +15605,14 @@ var IdentityStore = class {
|
|
|
14398
15605
|
peers = /* @__PURE__ */ new Map();
|
|
14399
15606
|
constructor(options = {}) {
|
|
14400
15607
|
const env = options.env ?? process.env;
|
|
14401
|
-
const dshHome = env.DSH_HOME ||
|
|
14402
|
-
this.directory = options.directory ??
|
|
15608
|
+
const dshHome = env.DSH_HOME || join2(options.homeDirectory ?? homedir(), ".dsh");
|
|
15609
|
+
this.directory = options.directory ?? join2(dshHome, "remote");
|
|
14403
15610
|
}
|
|
14404
15611
|
async loadOrCreate(deviceName) {
|
|
14405
15612
|
await mkdir(this.directory, { recursive: true, mode: 448 });
|
|
14406
15613
|
await chmod(this.directory, 448);
|
|
14407
|
-
const devicePath =
|
|
14408
|
-
const keyPath =
|
|
15614
|
+
const devicePath = join2(this.directory, "device.json");
|
|
15615
|
+
const keyPath = join2(this.directory, "device.key");
|
|
14409
15616
|
const [hasDevice, hasKey] = await Promise.all([exists(devicePath), exists(keyPath)]);
|
|
14410
15617
|
if (hasDevice !== hasKey) {
|
|
14411
15618
|
throw new IdentityInvalidError("device identity is incomplete; repair it explicitly before reconnecting");
|
|
@@ -14474,7 +15681,7 @@ var IdentityStore = class {
|
|
|
14474
15681
|
return removed;
|
|
14475
15682
|
}
|
|
14476
15683
|
async loadPeers() {
|
|
14477
|
-
const path =
|
|
15684
|
+
const path = join2(this.directory, "trusted-peers.json");
|
|
14478
15685
|
if (!await exists(path)) {
|
|
14479
15686
|
await atomicJsonWrite(path, { schemaVersion: 1, peers: [] }, 384);
|
|
14480
15687
|
}
|
|
@@ -14490,7 +15697,7 @@ var IdentityStore = class {
|
|
|
14490
15697
|
this.peers = peers;
|
|
14491
15698
|
}
|
|
14492
15699
|
async savePeers() {
|
|
14493
|
-
await atomicJsonWrite(
|
|
15700
|
+
await atomicJsonWrite(join2(this.directory, "trusted-peers.json"), {
|
|
14494
15701
|
schemaVersion: 1,
|
|
14495
15702
|
peers: [...this.peers.values()]
|
|
14496
15703
|
}, 384);
|
|
@@ -14499,7 +15706,7 @@ var IdentityStore = class {
|
|
|
14499
15706
|
function serverStorageDirectory(root, serverUrl, role) {
|
|
14500
15707
|
const origin = new URL(serverUrl).origin;
|
|
14501
15708
|
const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
|
|
14502
|
-
return
|
|
15709
|
+
return join2(root, "servers", scope, role);
|
|
14503
15710
|
}
|
|
14504
15711
|
function fingerprint(publicKey) {
|
|
14505
15712
|
const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
|
|
@@ -14517,7 +15724,7 @@ async function atomicJsonWrite(path, value, mode) {
|
|
|
14517
15724
|
`, mode);
|
|
14518
15725
|
}
|
|
14519
15726
|
async function atomicTextWrite(path, value, mode) {
|
|
14520
|
-
await mkdir(
|
|
15727
|
+
await mkdir(dirname2(path), { recursive: true, mode: 448 });
|
|
14521
15728
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
14522
15729
|
await writeFile(temporary, value, { encoding: "utf8", mode, flag: "wx" });
|
|
14523
15730
|
await chmod(temporary, mode);
|
|
@@ -14542,7 +15749,7 @@ function safeErrorMessage(error) {
|
|
|
14542
15749
|
|
|
14543
15750
|
// src/server-credentials.ts
|
|
14544
15751
|
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
14545
|
-
import { dirname as
|
|
15752
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
14546
15753
|
var credentialSchema = external_exports.object({
|
|
14547
15754
|
schemaVersion: external_exports.literal(1),
|
|
14548
15755
|
serverUrl: external_exports.string().url(),
|
|
@@ -14557,7 +15764,7 @@ var credentialSchema = external_exports.object({
|
|
|
14557
15764
|
var ServerCredentialStore = class {
|
|
14558
15765
|
path;
|
|
14559
15766
|
constructor(directory) {
|
|
14560
|
-
this.path =
|
|
15767
|
+
this.path = join3(directory, "server-credentials.json");
|
|
14561
15768
|
}
|
|
14562
15769
|
async load(serverUrl, deviceId) {
|
|
14563
15770
|
if (!await exists2(this.path)) return void 0;
|
|
@@ -14584,7 +15791,7 @@ var ServerCredentialsInvalidError = class extends Error {
|
|
|
14584
15791
|
code = "SERVER_CREDENTIALS_INVALID";
|
|
14585
15792
|
};
|
|
14586
15793
|
async function atomicWrite(path, contents) {
|
|
14587
|
-
await mkdir2(
|
|
15794
|
+
await mkdir2(dirname3(path), { recursive: true, mode: 448 });
|
|
14588
15795
|
const temporary = `${path}.${process.pid}.${uuidV7()}.tmp`;
|
|
14589
15796
|
await writeFile2(temporary, contents, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
14590
15797
|
await chmod2(temporary, 384);
|
|
@@ -15044,7 +16251,7 @@ function shortId2(value) {
|
|
|
15044
16251
|
|
|
15045
16252
|
// src/harness-version.ts
|
|
15046
16253
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
15047
|
-
import { dirname as
|
|
16254
|
+
import { dirname as dirname4, isAbsolute, join as join4 } from "node:path";
|
|
15048
16255
|
var LEGACY_PLACEHOLDER_VERSION = "0.0.1";
|
|
15049
16256
|
function normalizeHarnessVersion(value) {
|
|
15050
16257
|
if (typeof value !== "string") return void 0;
|
|
@@ -15058,14 +16265,14 @@ function selectHarnessVersion(reportedVersion, distributionVersion) {
|
|
|
15058
16265
|
}
|
|
15059
16266
|
async function readHarnessDistributionVersion(entrypoint = process.argv[1]) {
|
|
15060
16267
|
if (entrypoint === void 0 || !isAbsolute(entrypoint)) return void 0;
|
|
15061
|
-
let directory =
|
|
16268
|
+
let directory = dirname4(entrypoint);
|
|
15062
16269
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
15063
16270
|
try {
|
|
15064
|
-
const manifest = JSON.parse(await readFile3(
|
|
16271
|
+
const manifest = JSON.parse(await readFile3(join4(directory, "package.json"), "utf8"));
|
|
15065
16272
|
if (manifest.name === "@deepseek-ai/dsh") return normalizeHarnessVersion(manifest.version);
|
|
15066
16273
|
} catch {
|
|
15067
16274
|
}
|
|
15068
|
-
const parent =
|
|
16275
|
+
const parent = dirname4(directory);
|
|
15069
16276
|
if (parent === directory) break;
|
|
15070
16277
|
directory = parent;
|
|
15071
16278
|
}
|
|
@@ -15304,7 +16511,7 @@ function isOversizedListing(value) {
|
|
|
15304
16511
|
}
|
|
15305
16512
|
|
|
15306
16513
|
// src/server-connection.ts
|
|
15307
|
-
var DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS =
|
|
16514
|
+
var DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 12e3;
|
|
15308
16515
|
var HostServerConnection = class {
|
|
15309
16516
|
constructor(config, identity, identities, api, connections, logger, createWebSocket = (url) => new WebSocket(url), rtcFactoryProvider, hostCapabilities = () => ["harness.api.v1"], harnessVersion) {
|
|
15310
16517
|
this.config = config;
|
|
@@ -15594,6 +16801,7 @@ var HostServerConnection = class {
|
|
|
15594
16801
|
connectionId: payload.connectionId,
|
|
15595
16802
|
membershipId: descriptor.membershipId,
|
|
15596
16803
|
peer,
|
|
16804
|
+
preferredTransports: payload.preferredTransports,
|
|
15597
16805
|
noise,
|
|
15598
16806
|
transport: "negotiating"
|
|
15599
16807
|
});
|
|
@@ -15683,13 +16891,24 @@ var HostServerConnection = class {
|
|
|
15683
16891
|
code: errorCode(error)
|
|
15684
16892
|
});
|
|
15685
16893
|
}
|
|
16894
|
+
if (!tunnel.preferredTransports.includes("turn")) iceServers = stunOnlyIceServers(iceServers);
|
|
15686
16895
|
const rtc = new RtcDataChannelTransport({
|
|
15687
16896
|
role: "responder",
|
|
15688
16897
|
factory: this.rtcFactory,
|
|
15689
16898
|
iceServers,
|
|
15690
16899
|
onSignal: (signal) => this.sendRtcSignal(tunnel, signal),
|
|
15691
16900
|
negotiateTimeoutMs: DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS,
|
|
15692
|
-
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
|
+
}
|
|
15693
16912
|
});
|
|
15694
16913
|
tunnel.rtc = rtc;
|
|
15695
16914
|
rtc.onMessage((data) => tunnel.channel?.receive(void 0, data));
|
|
@@ -15731,7 +16950,8 @@ var HostServerConnection = class {
|
|
|
15731
16950
|
this.logger.info("webrtc data channel ready", {
|
|
15732
16951
|
connectionId: shortId3(tunnel.connectionId),
|
|
15733
16952
|
peerDeviceId: shortId3(tunnel.peer.deviceId),
|
|
15734
|
-
transport: tunnel.transportMode ?? selected
|
|
16953
|
+
transport: tunnel.transportMode ?? selected,
|
|
16954
|
+
...webrtcDiagnosticsLogFields2(rtcDiagnostics(tunnel.rtc))
|
|
15735
16955
|
});
|
|
15736
16956
|
}
|
|
15737
16957
|
async handleRtcFailed(tunnel, rtc, error) {
|
|
@@ -15739,7 +16959,8 @@ var HostServerConnection = class {
|
|
|
15739
16959
|
if (tunnel.transport === "p2p" || tunnel.transport === "turn") {
|
|
15740
16960
|
this.logger.warn("webrtc data channel failed; disconnecting peer", {
|
|
15741
16961
|
connectionId: shortId3(tunnel.connectionId),
|
|
15742
|
-
reason: diagnosticReason4(error)
|
|
16962
|
+
reason: diagnosticReason4(error),
|
|
16963
|
+
...webrtcDiagnosticsLogFields2(rtcDiagnostics(rtc))
|
|
15743
16964
|
});
|
|
15744
16965
|
await this.dropTunnel(tunnel.connectionId, "CONNECTION_FAILED");
|
|
15745
16966
|
return;
|
|
@@ -15749,7 +16970,8 @@ var HostServerConnection = class {
|
|
|
15749
16970
|
await rtc.close();
|
|
15750
16971
|
this.logger.warn("webrtc negotiation failed; falling back to relay", {
|
|
15751
16972
|
connectionId: shortId3(tunnel.connectionId),
|
|
15752
|
-
reason: diagnosticReason4(error)
|
|
16973
|
+
reason: diagnosticReason4(error),
|
|
16974
|
+
...webrtcDiagnosticsLogFields2(rtcDiagnostics(rtc))
|
|
15753
16975
|
});
|
|
15754
16976
|
}
|
|
15755
16977
|
sendTransportSelected(tunnel, transport) {
|
|
@@ -15976,6 +17198,28 @@ function diagnosticReason4(error) {
|
|
|
15976
17198
|
const message = asError2(error).message.replace(/[\r\n]+/g, " ").slice(0, 160);
|
|
15977
17199
|
return message || "Unknown Server connection error.";
|
|
15978
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
|
+
}
|
|
15979
17223
|
function errorCode(error) {
|
|
15980
17224
|
return error instanceof ServerApiError || error instanceof ControlConnectionError ? error.code : "CONNECTION_FAILED";
|
|
15981
17225
|
}
|
|
@@ -15993,7 +17237,7 @@ function closeCode(code) {
|
|
|
15993
17237
|
// src/remote-directory-browser.ts
|
|
15994
17238
|
import { readdir, stat as stat3 } from "node:fs/promises";
|
|
15995
17239
|
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
15996
|
-
import { basename, dirname as
|
|
17240
|
+
import { basename, dirname as dirname5, isAbsolute as isAbsolute2, parse, resolve } from "node:path";
|
|
15997
17241
|
var MAX_ENTRIES = 500;
|
|
15998
17242
|
async function listRemoteDirectory(path, signal) {
|
|
15999
17243
|
signal?.throwIfAborted();
|
|
@@ -16026,7 +17270,7 @@ function crumbs(path) {
|
|
|
16026
17270
|
let current = path;
|
|
16027
17271
|
while (current !== root) {
|
|
16028
17272
|
segments.unshift(basename(current));
|
|
16029
|
-
current =
|
|
17273
|
+
current = dirname5(current);
|
|
16030
17274
|
}
|
|
16031
17275
|
for (const segment of segments) {
|
|
16032
17276
|
current = resolve(current, segment);
|
|
@@ -16954,7 +18198,7 @@ var HostPluginRuntime = class {
|
|
|
16954
18198
|
this.connections,
|
|
16955
18199
|
this.logger,
|
|
16956
18200
|
void 0,
|
|
16957
|
-
this.config.forceRelay ? void 0 :
|
|
18201
|
+
this.config.forceRelay ? void 0 : () => loadNodeRtcFactory({ routeTargets: this.config.serverUrl === void 0 ? [] : [this.config.serverUrl] }),
|
|
16958
18202
|
() => this.fileViewerHost?.() === void 0 ? ["harness.api.v1"] : ["harness.api.v1", "fileviewer.read.v1"],
|
|
16959
18203
|
this.harnessVersion
|
|
16960
18204
|
);
|