ds-harness-remote 0.4.0 → 0.4.1
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.map +1 -1
- package/dist/client-runtime.js +10 -0
- package/dist/client-runtime.js.map +1 -1
- package/dist/index.js +231 -27
- package/dist/index.js.map +4 -4
- package/dist/native-rtc-helper.d.ts +10 -0
- package/dist/native-rtc-helper.d.ts.map +1 -1
- package/dist/native-rtc-helper.js +21 -3
- package/dist/native-rtc-helper.js.map +1 -1
- package/dist/server-connection.d.ts +5 -0
- package/dist/server-connection.d.ts.map +1 -1
- package/dist/server-connection.js +120 -5
- package/dist/server-connection.js.map +1 -1
- package/dist/typert-gateway-switch.d.ts.map +1 -1
- package/dist/typert-gateway-switch.js +10 -3
- package/dist/typert-gateway-switch.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4111,9 +4111,22 @@ function normalizeSdpMLineIndex(value) {
|
|
|
4111
4111
|
return null;
|
|
4112
4112
|
return value;
|
|
4113
4113
|
}
|
|
4114
|
+
function selectCapabilities(offered, supported) {
|
|
4115
|
+
const offeredCapabilities = new Set(offered);
|
|
4116
|
+
return [...new Set(supported)].filter((capability) => offeredCapabilities.has(capability));
|
|
4117
|
+
}
|
|
4118
|
+
function acceptNegotiatedCapabilities(offered, negotiated) {
|
|
4119
|
+
const accepted = negotiated ?? ["transport.relay"];
|
|
4120
|
+
if (selectCapabilities(accepted, offered).length !== accepted.length) {
|
|
4121
|
+
throw new Error("Server selected a capability that the peer did not offer.");
|
|
4122
|
+
}
|
|
4123
|
+
return [...accepted];
|
|
4124
|
+
}
|
|
4114
4125
|
var rpcMethodSchema = external_exports.enum(rpcMethods);
|
|
4115
4126
|
var messageTypeSchema = external_exports.enum(messageTypes);
|
|
4116
4127
|
var controlFrameTypeSchema = external_exports.enum(controlFrameTypes);
|
|
4128
|
+
var uniqueStrings = (values) => new Set(values).size === values.length;
|
|
4129
|
+
var uniqueNumbers = (values) => new Set(values).size === values.length;
|
|
4117
4130
|
var remoteMessageSchema = external_exports.object({
|
|
4118
4131
|
v: external_exports.literal(PROTOCOL_VERSION),
|
|
4119
4132
|
id: external_exports.string().min(1),
|
|
@@ -4134,8 +4147,8 @@ var helloPayloadSchema = external_exports.object({
|
|
|
4134
4147
|
role: external_exports.enum(["host", "client"]),
|
|
4135
4148
|
deviceId: external_exports.string().min(1),
|
|
4136
4149
|
accessToken: external_exports.string().min(1),
|
|
4137
|
-
protocols: external_exports.array(external_exports.number().int()).min(1),
|
|
4138
|
-
capabilities: external_exports.array(external_exports.string()),
|
|
4150
|
+
protocols: external_exports.array(external_exports.number().int().nonnegative().safe()).min(1).refine(uniqueNumbers),
|
|
4151
|
+
capabilities: external_exports.array(external_exports.string().min(1)).refine(uniqueStrings),
|
|
4139
4152
|
clientVersion: external_exports.string().optional(),
|
|
4140
4153
|
harnessVersion: external_exports.string().optional()
|
|
4141
4154
|
});
|
|
@@ -4146,6 +4159,7 @@ var helloAckPayloadSchema = external_exports.object({
|
|
|
4146
4159
|
heartbeatIntervalMs: external_exports.number().int().positive(),
|
|
4147
4160
|
maxControlFrameBytes: external_exports.number().int().positive(),
|
|
4148
4161
|
maxRelayFrameBytes: external_exports.number().int().positive(),
|
|
4162
|
+
capabilities: external_exports.array(external_exports.string().min(1)).refine(uniqueStrings).optional(),
|
|
4149
4163
|
webrtcEnabled: external_exports.boolean().optional(),
|
|
4150
4164
|
webrtcFallbackTimeoutMs: external_exports.number().int().positive().optional()
|
|
4151
4165
|
});
|
|
@@ -4177,7 +4191,7 @@ var secureHandshakePayloadSchema = external_exports.object({
|
|
|
4177
4191
|
var relayPayloadSchema = external_exports.object({
|
|
4178
4192
|
connectionId: external_exports.string().min(1),
|
|
4179
4193
|
targetDeviceId: external_exports.string().min(1),
|
|
4180
|
-
counter: external_exports.number().int().nonnegative(),
|
|
4194
|
+
counter: external_exports.number().int().nonnegative().safe(),
|
|
4181
4195
|
ciphertext: external_exports.string().min(1)
|
|
4182
4196
|
});
|
|
4183
4197
|
var signalPayloadSchema = external_exports.object({
|
|
@@ -4384,6 +4398,9 @@ function cryptoRandomId() {
|
|
|
4384
4398
|
return `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
4385
4399
|
}
|
|
4386
4400
|
|
|
4401
|
+
// ../client-core/dist/remote-gateway.js
|
|
4402
|
+
var DIRECT_REMOTE_CALL_BYTES = 2 * 1024 * 1024;
|
|
4403
|
+
|
|
4387
4404
|
// ../client-core/dist/index.js
|
|
4388
4405
|
var RemoteClientError = class extends Error {
|
|
4389
4406
|
code;
|
|
@@ -4925,6 +4942,8 @@ function isChunk(frame) {
|
|
|
4925
4942
|
|
|
4926
4943
|
// ../webrtc/dist/rtc-data-channel.js
|
|
4927
4944
|
var DEFAULT_NEGOTIATE_TIMEOUT_MS = 8e3;
|
|
4945
|
+
var SELECTED_PATH_RETRY_COUNT = 5;
|
|
4946
|
+
var SELECTED_PATH_RETRY_DELAY_MS = 50;
|
|
4928
4947
|
var RtcDataChannelTransport = class {
|
|
4929
4948
|
pc;
|
|
4930
4949
|
role;
|
|
@@ -5254,7 +5273,11 @@ var RtcDataChannelTransport = class {
|
|
|
5254
5273
|
}
|
|
5255
5274
|
async resolveSelectedTransport() {
|
|
5256
5275
|
try {
|
|
5257
|
-
|
|
5276
|
+
let selected = await this.refreshStatsDiagnostics();
|
|
5277
|
+
for (let attempt = 0; attempt < SELECTED_PATH_RETRY_COUNT && !this.closed && (selected.transport === void 0 || selected.mode === void 0); attempt += 1) {
|
|
5278
|
+
await sleep(SELECTED_PATH_RETRY_DELAY_MS);
|
|
5279
|
+
selected = await this.refreshStatsDiagnostics();
|
|
5280
|
+
}
|
|
5258
5281
|
this.selected = selected?.transport;
|
|
5259
5282
|
this.selectedMode = selected?.mode;
|
|
5260
5283
|
this.selectedPathTelemetry = selectedPathTelemetry(selected);
|
|
@@ -5418,6 +5441,8 @@ function inspectSelectedPath(stats) {
|
|
|
5418
5441
|
const remote = candidates.get(String(pair.remoteCandidateId));
|
|
5419
5442
|
const localType = local?.candidateType;
|
|
5420
5443
|
const remoteType = remote?.candidateType;
|
|
5444
|
+
const localAddressScope = summarizeAddress(candidateAddressValue(local)).addressScope;
|
|
5445
|
+
const remoteAddressScope = summarizeAddress(candidateAddressValue(remote)).addressScope;
|
|
5421
5446
|
let selected;
|
|
5422
5447
|
if (localType === "relay" || remoteType === "relay")
|
|
5423
5448
|
selected = { transport: "turn", mode: "TURN" };
|
|
@@ -5425,13 +5450,15 @@ function inspectSelectedPath(stats) {
|
|
|
5425
5450
|
continue;
|
|
5426
5451
|
selected ??= {
|
|
5427
5452
|
transport: "p2p",
|
|
5428
|
-
mode: localType
|
|
5453
|
+
mode: isLanCandidatePair(localType, remoteType, localAddressScope, remoteAddressScope) ? "LAN" : "P2P"
|
|
5429
5454
|
};
|
|
5430
5455
|
const currentRoundTripTime = numberStat(pair, "currentRoundTripTime");
|
|
5431
5456
|
return {
|
|
5432
5457
|
...selected,
|
|
5433
5458
|
localCandidateType: stringStat(local, "candidateType"),
|
|
5434
5459
|
remoteCandidateType: stringStat(remote, "candidateType"),
|
|
5460
|
+
localAddressScope,
|
|
5461
|
+
remoteAddressScope,
|
|
5435
5462
|
localAddress: candidateAddress(local),
|
|
5436
5463
|
remoteAddress: candidateAddress(remote),
|
|
5437
5464
|
protocol: stringStat(local, "protocol") ?? stringStat(remote, "protocol"),
|
|
@@ -5556,6 +5583,19 @@ function candidateTypeStat(candidate) {
|
|
|
5556
5583
|
function candidateAddressValue(candidate) {
|
|
5557
5584
|
return stringStat(candidate, "address") ?? stringStat(candidate, "ip");
|
|
5558
5585
|
}
|
|
5586
|
+
function isLanCandidatePair(localType, remoteType, localScope, remoteScope) {
|
|
5587
|
+
if (localType === "host" && remoteType === "host")
|
|
5588
|
+
return true;
|
|
5589
|
+
const directTypes = /* @__PURE__ */ new Set(["host", "prflx"]);
|
|
5590
|
+
if (!directTypes.has(String(localType)) || !directTypes.has(String(remoteType)))
|
|
5591
|
+
return false;
|
|
5592
|
+
if (isLocalNetworkScope(localScope) && isLocalNetworkScope(remoteScope))
|
|
5593
|
+
return true;
|
|
5594
|
+
return localType === "host" && isLocalNetworkScope(localScope) && remoteType === "prflx" && remoteScope === "unknown" || remoteType === "host" && isLocalNetworkScope(remoteScope) && localType === "prflx" && localScope === "unknown";
|
|
5595
|
+
}
|
|
5596
|
+
function isLocalNetworkScope(scope) {
|
|
5597
|
+
return scope === "private" || scope === "link-local";
|
|
5598
|
+
}
|
|
5559
5599
|
function increment(map, key) {
|
|
5560
5600
|
map[key] = (map[key] ?? 0) + 1;
|
|
5561
5601
|
}
|
|
@@ -5565,6 +5605,8 @@ function selectedPathTelemetry(details) {
|
|
|
5565
5605
|
...details.mode === void 0 ? {} : { mode: details.mode },
|
|
5566
5606
|
...details.localCandidateType === void 0 ? {} : { localCandidateType: details.localCandidateType },
|
|
5567
5607
|
...details.remoteCandidateType === void 0 ? {} : { remoteCandidateType: details.remoteCandidateType },
|
|
5608
|
+
...details.localAddressScope === void 0 ? {} : { localAddressScope: details.localAddressScope },
|
|
5609
|
+
...details.remoteAddressScope === void 0 ? {} : { remoteAddressScope: details.remoteAddressScope },
|
|
5568
5610
|
...details.protocol === void 0 ? {} : { protocol: details.protocol },
|
|
5569
5611
|
...details.relayProtocol === void 0 ? {} : { relayProtocol: details.relayProtocol },
|
|
5570
5612
|
...details.currentRoundTripTimeMs === void 0 ? {} : { currentRoundTripTimeMs: details.currentRoundTripTimeMs },
|
|
@@ -5609,6 +5651,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5609
5651
|
handshakeTimer;
|
|
5610
5652
|
webrtcEnabled = true;
|
|
5611
5653
|
serverNegotiateTimeoutMs;
|
|
5654
|
+
negotiatedCapabilities = ["transport.relay"];
|
|
5612
5655
|
connectedAt;
|
|
5613
5656
|
lastRtcDiagnostics;
|
|
5614
5657
|
constructor(url, options) {
|
|
@@ -5687,6 +5730,8 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5687
5730
|
controlChannelUrl: this.url,
|
|
5688
5731
|
controlChannelState: socketState(this.socket?.readyState),
|
|
5689
5732
|
preferredTransports: this.options.forceRelay === true ? ["relay"] : [...this.options.preferredTransports ?? DEFAULT_PREFERRED_TRANSPORTS],
|
|
5733
|
+
negotiatedCapabilities: [...this.negotiatedCapabilities],
|
|
5734
|
+
webRtcEnabled: this.webrtcEnabled,
|
|
5690
5735
|
...this.rtc === void 0 ? {} : { webRtc: await this.rtc.connectionDetails() }
|
|
5691
5736
|
};
|
|
5692
5737
|
}
|
|
@@ -5733,6 +5778,12 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5733
5778
|
const payload = frame.payload;
|
|
5734
5779
|
if (payload.protocol !== PROTOCOL_VERSION)
|
|
5735
5780
|
throw new Error("Server selected an unsupported protocol version");
|
|
5781
|
+
const offered = this.options.capabilities ?? DEFAULT_CAPABILITIES;
|
|
5782
|
+
this.negotiatedCapabilities = acceptNegotiatedCapabilities(offered, payload.capabilities);
|
|
5783
|
+
const preferredTransports = this.negotiatedTransports();
|
|
5784
|
+
if (preferredTransports.length === 0) {
|
|
5785
|
+
throw new Error("Server did not negotiate a supported transport capability");
|
|
5786
|
+
}
|
|
5736
5787
|
if (payload.webrtcEnabled === false)
|
|
5737
5788
|
this.webrtcEnabled = false;
|
|
5738
5789
|
if (typeof payload.webrtcFallbackTimeoutMs === "number" && Number.isSafeInteger(payload.webrtcFallbackTimeoutMs) && payload.webrtcFallbackTimeoutMs > 0) {
|
|
@@ -5740,7 +5791,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5740
5791
|
}
|
|
5741
5792
|
this.sendControl("connect.request", {
|
|
5742
5793
|
hostDeviceId: this.options.targetDeviceId,
|
|
5743
|
-
preferredTransports
|
|
5794
|
+
preferredTransports
|
|
5744
5795
|
});
|
|
5745
5796
|
return;
|
|
5746
5797
|
}
|
|
@@ -5750,7 +5801,9 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5750
5801
|
throw new Error("connect.accepted did not include a connectionId");
|
|
5751
5802
|
}
|
|
5752
5803
|
this.connectionId = payload.connectionId;
|
|
5753
|
-
void this.negotiate()
|
|
5804
|
+
void this.negotiate().catch((error) => {
|
|
5805
|
+
this.failConnection(error instanceof Error ? error : new Error("Adaptive transport negotiation failed"));
|
|
5806
|
+
});
|
|
5754
5807
|
return;
|
|
5755
5808
|
}
|
|
5756
5809
|
if (frame.type === "connect.rejected" || frame.type === "error") {
|
|
@@ -5807,15 +5860,25 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5807
5860
|
async negotiate() {
|
|
5808
5861
|
if (this.connectionId === void 0)
|
|
5809
5862
|
return;
|
|
5810
|
-
const preferred = this.
|
|
5863
|
+
const preferred = this.negotiatedTransports();
|
|
5811
5864
|
const wantWebRtc = !this.options.forceRelay && this.webrtcEnabled && (preferred.includes("lan") || preferred.includes("p2p") || preferred.includes("turn"));
|
|
5812
5865
|
let selected = "relay";
|
|
5813
5866
|
this.lastRtcDiagnostics = void 0;
|
|
5814
5867
|
if (wantWebRtc) {
|
|
5815
5868
|
try {
|
|
5816
5869
|
const rtcSelected = await this.tryWebRtc();
|
|
5817
|
-
|
|
5818
|
-
|
|
5870
|
+
if (!preferred.includes(rtcSelected)) {
|
|
5871
|
+
await this.rtc?.close();
|
|
5872
|
+
this.rtc = void 0;
|
|
5873
|
+
if (!preferred.includes("relay")) {
|
|
5874
|
+
throw new Error(`WebRTC selected unnegotiated transport: ${rtcSelected}`);
|
|
5875
|
+
}
|
|
5876
|
+
this.dataMode = "relay";
|
|
5877
|
+
selected = "relay";
|
|
5878
|
+
} else {
|
|
5879
|
+
this.dataMode = "webrtc";
|
|
5880
|
+
selected = rtcSelected;
|
|
5881
|
+
}
|
|
5819
5882
|
} catch (error) {
|
|
5820
5883
|
const reason = error instanceof Error ? error : new Error("WebRTC negotiation failed.");
|
|
5821
5884
|
try {
|
|
@@ -5824,10 +5887,14 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5824
5887
|
}
|
|
5825
5888
|
await this.rtc?.close();
|
|
5826
5889
|
this.rtc = void 0;
|
|
5890
|
+
if (!preferred.includes("relay"))
|
|
5891
|
+
throw reason;
|
|
5827
5892
|
this.dataMode = "relay";
|
|
5828
5893
|
selected = "relay";
|
|
5829
5894
|
}
|
|
5830
5895
|
} else {
|
|
5896
|
+
if (!preferred.includes("relay"))
|
|
5897
|
+
throw new Error("No negotiated transport is available");
|
|
5831
5898
|
this.dataMode = "relay";
|
|
5832
5899
|
selected = "relay";
|
|
5833
5900
|
}
|
|
@@ -5839,6 +5906,14 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5839
5906
|
});
|
|
5840
5907
|
this.finishConnection();
|
|
5841
5908
|
}
|
|
5909
|
+
negotiatedTransports() {
|
|
5910
|
+
const preferred = this.options.forceRelay === true ? ["relay"] : this.options.preferredTransports ?? DEFAULT_PREFERRED_TRANSPORTS;
|
|
5911
|
+
return preferred.filter((transport) => {
|
|
5912
|
+
if (transport === "lan" || transport === "p2p")
|
|
5913
|
+
return this.negotiatedCapabilities.includes("transport.p2p");
|
|
5914
|
+
return this.negotiatedCapabilities.includes(`transport.${transport}`);
|
|
5915
|
+
});
|
|
5916
|
+
}
|
|
5842
5917
|
async tryWebRtc() {
|
|
5843
5918
|
const connectionId = this.connectionId;
|
|
5844
5919
|
if (connectionId === void 0)
|
|
@@ -13470,8 +13545,8 @@ function base64ToBytes(value) {
|
|
|
13470
13545
|
}
|
|
13471
13546
|
|
|
13472
13547
|
// src/remote-typert-gateway.ts
|
|
13473
|
-
var
|
|
13474
|
-
var
|
|
13548
|
+
var DIRECT_REMOTE_CALL_BYTES2 = 2 * 1024 * 1024;
|
|
13549
|
+
var RemoteTypertGateway2 = class {
|
|
13475
13550
|
constructor(client) {
|
|
13476
13551
|
this.client = client;
|
|
13477
13552
|
}
|
|
@@ -13488,7 +13563,7 @@ var RemoteTypertGateway = class {
|
|
|
13488
13563
|
const request = { endpoint, payload };
|
|
13489
13564
|
const encoded = new TextEncoder().encode(JSON.stringify(request));
|
|
13490
13565
|
let response;
|
|
13491
|
-
if (encoded.byteLength >
|
|
13566
|
+
if (encoded.byteLength > DIRECT_REMOTE_CALL_BYTES2) {
|
|
13492
13567
|
response = await this.callTransferred(encoded, signal);
|
|
13493
13568
|
} else {
|
|
13494
13569
|
try {
|
|
@@ -13770,7 +13845,7 @@ function normalizeServerUrl(value) {
|
|
|
13770
13845
|
}
|
|
13771
13846
|
|
|
13772
13847
|
// src/version.ts
|
|
13773
|
-
var PLUGIN_VERSION = "0.4.
|
|
13848
|
+
var PLUGIN_VERSION = "0.4.1";
|
|
13774
13849
|
|
|
13775
13850
|
// src/server-api.ts
|
|
13776
13851
|
var HostServerApi = class {
|
|
@@ -14169,6 +14244,7 @@ function requireRecord(value, name2) {
|
|
|
14169
14244
|
|
|
14170
14245
|
// src/typert-gateway-switch.ts
|
|
14171
14246
|
var REMOTE_COMMAND_METHODS = ["execute", "list"];
|
|
14247
|
+
var LOCAL_ONLY_NAMESPACES = /* @__PURE__ */ new Set(["dynamicCordisRunner"]);
|
|
14172
14248
|
var ALL_REMOTE_COMMANDS = { execute: true, list: true };
|
|
14173
14249
|
var TypertGatewaySwitch = class {
|
|
14174
14250
|
runtime;
|
|
@@ -14229,13 +14305,13 @@ var TypertGatewaySwitch = class {
|
|
|
14229
14305
|
if (this.installed) return;
|
|
14230
14306
|
this.runtime.invoke = (request) => this.selectInvoke(request);
|
|
14231
14307
|
if (this.originalStream !== void 0) {
|
|
14232
|
-
this.runtime.stream = (request) => this.remoteTarget === void 0 ? this.localStream(request) : this.remoteTarget.open(endpointOf(request), { args: request.args }, request.signal ?? new AbortController().signal);
|
|
14308
|
+
this.runtime.stream = (request) => this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpointOf(request)) ? this.localStream(request) : this.remoteTarget.open(endpointOf(request), { args: request.args }, request.signal ?? new AbortController().signal);
|
|
14233
14309
|
}
|
|
14234
14310
|
if (this.originalDispatch !== void 0) {
|
|
14235
|
-
this.runtime.dispatchRpc = (endpoint, payload, signal) => this.remoteTarget === void 0 ? this.localDispatch(endpoint, payload, signal) : this.remoteTarget.dispatch(endpoint, payload, signal);
|
|
14311
|
+
this.runtime.dispatchRpc = (endpoint, payload, signal) => this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpoint) ? this.localDispatch(endpoint, payload, signal) : this.remoteTarget.dispatch(endpoint, payload, signal);
|
|
14236
14312
|
}
|
|
14237
14313
|
if (this.originalOpen !== void 0) {
|
|
14238
|
-
this.runtime.openWireStream = (endpoint, payload, signal) => this.remoteTarget === void 0 ? this.localOpen(endpoint, payload, signal) : this.remoteTarget.open(endpoint, payload, signal);
|
|
14314
|
+
this.runtime.openWireStream = (endpoint, payload, signal) => this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpoint) ? this.localOpen(endpoint, payload, signal) : this.remoteTarget.open(endpoint, payload, signal);
|
|
14239
14315
|
}
|
|
14240
14316
|
this.installed = true;
|
|
14241
14317
|
}
|
|
@@ -14262,6 +14338,7 @@ var TypertGatewaySwitch = class {
|
|
|
14262
14338
|
this.installed = false;
|
|
14263
14339
|
}
|
|
14264
14340
|
selectInvoke(request) {
|
|
14341
|
+
if (isLocalOnlyEndpoint(endpointOf(request))) return this.localInvoke(request);
|
|
14265
14342
|
if (this.remoteTarget !== void 0) return this.remoteTarget.invoke(request);
|
|
14266
14343
|
if (request.namespace !== "commands" || !isRemoteCommandMethod(request.method) || this.remoteInvoke === void 0) {
|
|
14267
14344
|
return this.localInvoke(request);
|
|
@@ -14292,6 +14369,10 @@ function requestFromCarrier(endpoint, payload, signal) {
|
|
|
14292
14369
|
function endpointOf(request) {
|
|
14293
14370
|
return `${request.namespace}/${request.method}`;
|
|
14294
14371
|
}
|
|
14372
|
+
function isLocalOnlyEndpoint(endpoint) {
|
|
14373
|
+
const separator = endpoint.indexOf("/");
|
|
14374
|
+
return separator > 0 && LOCAL_ONLY_NAMESPACES.has(endpoint.slice(0, separator));
|
|
14375
|
+
}
|
|
14295
14376
|
function isRemoteCommandMethod(method) {
|
|
14296
14377
|
return REMOTE_COMMAND_METHODS.includes(method);
|
|
14297
14378
|
}
|
|
@@ -14305,7 +14386,7 @@ import { networkInterfaces } from "node:os";
|
|
|
14305
14386
|
|
|
14306
14387
|
// src/native-rtc-helper.ts
|
|
14307
14388
|
import { spawn, spawnSync } from "node:child_process";
|
|
14308
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
14389
|
+
import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
14309
14390
|
import { delimiter, dirname, join } from "node:path";
|
|
14310
14391
|
import { fileURLToPath } from "node:url";
|
|
14311
14392
|
var cachedExternalFactory;
|
|
@@ -14321,17 +14402,25 @@ async function loadExternalNativeRtcFactory() {
|
|
|
14321
14402
|
cachedExternalFactory = buildExternalNativeRtcFactory(nodeBinary);
|
|
14322
14403
|
return cachedExternalFactory;
|
|
14323
14404
|
}
|
|
14324
|
-
function buildExternalNativeRtcFactory(nodeBinary, requireFrom =
|
|
14405
|
+
function buildExternalNativeRtcFactory(nodeBinary, requireFrom = resolveNativeRtcRequireFrom()) {
|
|
14325
14406
|
return {
|
|
14326
14407
|
create(configuration) {
|
|
14327
14408
|
return new ExternalNativePeerConnection(nodeBinary, requireFrom, configuration);
|
|
14328
14409
|
}
|
|
14329
14410
|
};
|
|
14330
14411
|
}
|
|
14412
|
+
function resolveNativeRtcRequireFrom(moduleUrl = import.meta.url) {
|
|
14413
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
14414
|
+
try {
|
|
14415
|
+
return realpathSync(modulePath);
|
|
14416
|
+
} catch {
|
|
14417
|
+
return modulePath;
|
|
14418
|
+
}
|
|
14419
|
+
}
|
|
14331
14420
|
function resolveExternalNodeBinaryForRtc() {
|
|
14332
14421
|
if (cachedNodeBinaryResolved) return cachedNodeBinary;
|
|
14333
14422
|
cachedNodeBinaryResolved = true;
|
|
14334
|
-
const requireFrom =
|
|
14423
|
+
const requireFrom = resolveNativeRtcRequireFrom();
|
|
14335
14424
|
for (const candidate of nodeBinaryCandidates()) {
|
|
14336
14425
|
if (isUsableExternalNode(candidate, requireFrom)) {
|
|
14337
14426
|
cachedNodeBinary = candidate;
|
|
@@ -15520,7 +15609,7 @@ var ClientModeRuntime = class {
|
|
|
15520
15609
|
async listRemoteDirectory(targetDeviceId, path, signal) {
|
|
15521
15610
|
const remote = await this.ensureConnected(targetDeviceId, signal);
|
|
15522
15611
|
if (remote.features.remoteGateway) {
|
|
15523
|
-
const value = await new
|
|
15612
|
+
const value = await new RemoteTypertGateway2(remote.client).invoke({
|
|
15524
15613
|
namespace: "directoryPicker",
|
|
15525
15614
|
method: "list",
|
|
15526
15615
|
args: path === void 0 ? {} : { path },
|
|
@@ -15538,7 +15627,7 @@ var ClientModeRuntime = class {
|
|
|
15538
15627
|
async listRemoteWorkspaces(targetDeviceId, signal) {
|
|
15539
15628
|
const remote = await this.ensureConnected(targetDeviceId, signal);
|
|
15540
15629
|
if (remote.features.remoteGateway) {
|
|
15541
|
-
return readRemoteWorkspaceBaseline(new
|
|
15630
|
+
return readRemoteWorkspaceBaseline(new RemoteTypertGateway2(remote.client), signal);
|
|
15542
15631
|
}
|
|
15543
15632
|
const api = new RemoteHarnessApiProxy(remote.client).api;
|
|
15544
15633
|
const response = await api.workspace.list({
|
|
@@ -15554,7 +15643,7 @@ var ClientModeRuntime = class {
|
|
|
15554
15643
|
this.assertRemoteCompatible(remote);
|
|
15555
15644
|
let workspace;
|
|
15556
15645
|
if (remote.features.remoteGateway) {
|
|
15557
|
-
workspace = await new
|
|
15646
|
+
workspace = await new RemoteTypertGateway2(remote.client).invoke({
|
|
15558
15647
|
namespace: "workspace",
|
|
15559
15648
|
method: "create",
|
|
15560
15649
|
args: { request: { path } },
|
|
@@ -15605,7 +15694,7 @@ var ClientModeRuntime = class {
|
|
|
15605
15694
|
selectRemoteTarget(remote) {
|
|
15606
15695
|
const target = { deviceId: remote.target.deviceId, name: remote.target.name };
|
|
15607
15696
|
if (this.gatewaySwitch.supportsCarrier()) {
|
|
15608
|
-
this.gatewaySwitch.selectRemote(new
|
|
15697
|
+
this.gatewaySwitch.selectRemote(new RemoteTypertGateway2(remote.client), void 0, target);
|
|
15609
15698
|
return;
|
|
15610
15699
|
}
|
|
15611
15700
|
this.proxySwitch.selectRemote(new RemoteHarnessApiProxy(remote.client).api, target);
|
|
@@ -15634,6 +15723,11 @@ var ClientModeRuntime = class {
|
|
|
15634
15723
|
if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
|
|
15635
15724
|
const credentials = await this.server.authenticate(identity);
|
|
15636
15725
|
const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => void 0);
|
|
15726
|
+
if (!this.config.forceRelay && rtcFactory === void 0) {
|
|
15727
|
+
this.logger.warn("remote Harness WebRTC backend unavailable; using relay", {
|
|
15728
|
+
targetDeviceId: shortId(target.deviceId)
|
|
15729
|
+
});
|
|
15730
|
+
}
|
|
15637
15731
|
let webRtcFallback = false;
|
|
15638
15732
|
const createTransport = (attempt) => new AdaptiveTransport(
|
|
15639
15733
|
websocketUrl(this.server.baseUrl),
|
|
@@ -15698,6 +15792,11 @@ var ClientModeRuntime = class {
|
|
|
15698
15792
|
this.logger.info("remote Harness transport ready", {
|
|
15699
15793
|
targetDeviceId: shortId(target.deviceId),
|
|
15700
15794
|
transport: connectedClient.getStats().mode,
|
|
15795
|
+
...connectionDetails === void 0 ? {} : {
|
|
15796
|
+
preferredTransports: connectionDetails.preferredTransports,
|
|
15797
|
+
negotiatedCapabilities: connectionDetails.negotiatedCapabilities,
|
|
15798
|
+
webRtcEnabled: connectionDetails.webRtcEnabled
|
|
15799
|
+
},
|
|
15701
15800
|
...webrtcDiagnosticsLogFields(connectionDetails?.webRtc?.diagnostics)
|
|
15702
15801
|
});
|
|
15703
15802
|
const features = await probeRemoteHostFeatures(connectedClient, serverDevice.clientVersion);
|
|
@@ -17026,6 +17125,7 @@ var HostServerConnection = class {
|
|
|
17026
17125
|
reconnectRequested = false;
|
|
17027
17126
|
resumeQueued = false;
|
|
17028
17127
|
rtcFactory;
|
|
17128
|
+
negotiatedCapabilities = ["transport.relay"];
|
|
17029
17129
|
start() {
|
|
17030
17130
|
if (this.running !== void 0) return;
|
|
17031
17131
|
this.stopped = false;
|
|
@@ -17115,6 +17215,7 @@ var HostServerConnection = class {
|
|
|
17115
17215
|
async connectOnce() {
|
|
17116
17216
|
const credentials = await this.api.authenticate(this.identity);
|
|
17117
17217
|
if (this.stopped) return;
|
|
17218
|
+
const offeredCapabilities = this.rtcFactoryProvider === void 0 || this.config.forceRelay ? ["transport.relay", ...this.hostCapabilities()] : ["transport.p2p", "transport.turn", "transport.relay", ...this.hostCapabilities()];
|
|
17118
17219
|
const socket = this.createWebSocket(websocketUrl2(this.api.baseUrl));
|
|
17119
17220
|
this.socket = socket;
|
|
17120
17221
|
let acknowledged = false;
|
|
@@ -17138,7 +17239,7 @@ var HostServerConnection = class {
|
|
|
17138
17239
|
protocols: [PROTOCOL_VERSION],
|
|
17139
17240
|
clientVersion: PLUGIN_VERSION,
|
|
17140
17241
|
...this.harnessVersion === void 0 ? {} : { harnessVersion: this.harnessVersion },
|
|
17141
|
-
capabilities:
|
|
17242
|
+
capabilities: offeredCapabilities
|
|
17142
17243
|
});
|
|
17143
17244
|
};
|
|
17144
17245
|
socket.onmessage = (event) => {
|
|
@@ -17147,6 +17248,10 @@ var HostServerConnection = class {
|
|
|
17147
17248
|
this.lastActiveAt = Date.now();
|
|
17148
17249
|
if (frame.type === "hello.ack") {
|
|
17149
17250
|
const payload = requireHelloAck(frame.payload);
|
|
17251
|
+
this.negotiatedCapabilities = acceptNegotiatedCapabilities(offeredCapabilities, payload.capabilities);
|
|
17252
|
+
if (!this.negotiatedCapabilities.some((capability) => capability === "transport.relay" || capability === "transport.p2p" || capability === "transport.turn")) {
|
|
17253
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Server did not negotiate a transport capability.");
|
|
17254
|
+
}
|
|
17150
17255
|
acknowledged = true;
|
|
17151
17256
|
clearTimeout(helloTimer);
|
|
17152
17257
|
this.online = true;
|
|
@@ -17222,7 +17327,10 @@ var HostServerConnection = class {
|
|
|
17222
17327
|
this.handleSignalIce(requireSignalIce(frame.payload));
|
|
17223
17328
|
return;
|
|
17224
17329
|
}
|
|
17225
|
-
if (frame.type === "transport.selected")
|
|
17330
|
+
if (frame.type === "transport.selected") {
|
|
17331
|
+
await this.handleTransportSelected(requireTransportSelected(frame.payload));
|
|
17332
|
+
return;
|
|
17333
|
+
}
|
|
17226
17334
|
if (frame.type === "signal.answer") return;
|
|
17227
17335
|
if (frame.type === "error") {
|
|
17228
17336
|
const payload = requireControlError(frame.payload);
|
|
@@ -17309,6 +17417,17 @@ var HostServerConnection = class {
|
|
|
17309
17417
|
if (tunnel === void 0 || payload.targetDeviceId !== this.identity.deviceId || payload.step !== 1) {
|
|
17310
17418
|
throw new ControlConnectionError("SECURE_CHANNEL_FAILED", "Noise IK handshake is not valid for this connection.");
|
|
17311
17419
|
}
|
|
17420
|
+
if (tunnel.transport === "negotiating" && tunnel.rtc !== void 0) {
|
|
17421
|
+
if (tunnel.pendingHandshake === void 0) tunnel.pendingHandshake = payload;
|
|
17422
|
+
else this.logger.warn("duplicate pending secure handshake ignored", {
|
|
17423
|
+
connectionId: shortId3(tunnel.connectionId),
|
|
17424
|
+
peerDeviceId: shortId3(tunnel.peer.deviceId)
|
|
17425
|
+
});
|
|
17426
|
+
return;
|
|
17427
|
+
}
|
|
17428
|
+
await this.completeHandshake(tunnel, payload);
|
|
17429
|
+
}
|
|
17430
|
+
async completeHandshake(tunnel, payload) {
|
|
17312
17431
|
tunnel.noise.readHandshake(fromBase64Url2(payload.data));
|
|
17313
17432
|
const reply = tunnel.noise.writeHandshake();
|
|
17314
17433
|
if (!tunnel.noise.complete) throw new ControlConnectionError("SECURE_CHANNEL_FAILED", "Noise IK handshake did not complete.");
|
|
@@ -17333,7 +17452,16 @@ var HostServerConnection = class {
|
|
|
17333
17452
|
transport: mode
|
|
17334
17453
|
});
|
|
17335
17454
|
}
|
|
17455
|
+
async resumePendingHandshake(tunnel) {
|
|
17456
|
+
const payload = tunnel.pendingHandshake;
|
|
17457
|
+
if (payload === void 0 || tunnel.channel !== void 0 || tunnel.transport === "negotiating") return;
|
|
17458
|
+
tunnel.pendingHandshake = void 0;
|
|
17459
|
+
await this.completeHandshake(tunnel, payload);
|
|
17460
|
+
}
|
|
17336
17461
|
async handleRelay(payload) {
|
|
17462
|
+
if (!this.negotiatedCapabilities.includes("transport.relay")) {
|
|
17463
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Server forwarded Relay without negotiating it.");
|
|
17464
|
+
}
|
|
17337
17465
|
if (payload.targetDeviceId !== this.identity.deviceId) {
|
|
17338
17466
|
throw new ControlConnectionError("INVALID_MESSAGE", "Relay frame target does not match this Host.");
|
|
17339
17467
|
}
|
|
@@ -17352,6 +17480,9 @@ var HostServerConnection = class {
|
|
|
17352
17480
|
}
|
|
17353
17481
|
}
|
|
17354
17482
|
async handleSignalOffer(payload) {
|
|
17483
|
+
if (!this.canUseWebRtc()) {
|
|
17484
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Server forwarded WebRTC signaling without negotiating it.");
|
|
17485
|
+
}
|
|
17355
17486
|
const tunnel = this.tunnels.get(payload.connectionId);
|
|
17356
17487
|
if (tunnel === void 0 || payload.targetDeviceId !== this.identity.deviceId) {
|
|
17357
17488
|
this.logger.warn("stale webrtc offer ignored", { connectionId: shortId3(payload.connectionId) });
|
|
@@ -17413,10 +17544,54 @@ var HostServerConnection = class {
|
|
|
17413
17544
|
rtc.handleSignal({ type: "offer", sdp: payload.sdp });
|
|
17414
17545
|
}
|
|
17415
17546
|
handleSignalIce(payload) {
|
|
17547
|
+
if (!this.canUseWebRtc()) {
|
|
17548
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Server forwarded WebRTC signaling without negotiating it.");
|
|
17549
|
+
}
|
|
17416
17550
|
const tunnel = this.tunnels.get(payload.connectionId);
|
|
17417
17551
|
if (tunnel === void 0 || payload.targetDeviceId !== this.identity.deviceId) return;
|
|
17418
17552
|
tunnel.rtc?.handleSignal({ type: "ice", candidate: payload.candidate });
|
|
17419
17553
|
}
|
|
17554
|
+
async handleTransportSelected(payload) {
|
|
17555
|
+
const tunnel = this.tunnels.get(payload.connectionId);
|
|
17556
|
+
if (tunnel === void 0 || payload.targetDeviceId !== this.identity.deviceId) {
|
|
17557
|
+
this.logger.warn("stale transport selection ignored", { connectionId: shortId3(payload.connectionId) });
|
|
17558
|
+
return;
|
|
17559
|
+
}
|
|
17560
|
+
if (tunnel.channel !== void 0) {
|
|
17561
|
+
if (tunnel.transport !== payload.transport) {
|
|
17562
|
+
this.logger.warn("late transport selection ignored", {
|
|
17563
|
+
connectionId: shortId3(tunnel.connectionId),
|
|
17564
|
+
selected: payload.transport
|
|
17565
|
+
});
|
|
17566
|
+
}
|
|
17567
|
+
return;
|
|
17568
|
+
}
|
|
17569
|
+
const requiredCapability = `transport.${payload.transport}`;
|
|
17570
|
+
if (!this.negotiatedCapabilities.includes(requiredCapability)) {
|
|
17571
|
+
throw new ControlConnectionError(
|
|
17572
|
+
"INVALID_MESSAGE",
|
|
17573
|
+
`Client selected unnegotiated transport: ${payload.transport}`
|
|
17574
|
+
);
|
|
17575
|
+
}
|
|
17576
|
+
if (payload.transport === "relay") {
|
|
17577
|
+
const rtc = tunnel.rtc;
|
|
17578
|
+
tunnel.rtc = void 0;
|
|
17579
|
+
tunnel.transport = "relay";
|
|
17580
|
+
tunnel.transportMode = void 0;
|
|
17581
|
+
await rtc?.close();
|
|
17582
|
+
await this.resumePendingHandshake(tunnel);
|
|
17583
|
+
return;
|
|
17584
|
+
}
|
|
17585
|
+
if (tunnel.rtc === void 0) {
|
|
17586
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Client selected WebRTC before creating a data channel.");
|
|
17587
|
+
}
|
|
17588
|
+
tunnel.transport = payload.transport;
|
|
17589
|
+
tunnel.transportMode = tunnel.rtc.selectedPathMode();
|
|
17590
|
+
await this.resumePendingHandshake(tunnel);
|
|
17591
|
+
}
|
|
17592
|
+
canUseWebRtc() {
|
|
17593
|
+
return this.negotiatedCapabilities.includes("transport.p2p") || this.negotiatedCapabilities.includes("transport.turn");
|
|
17594
|
+
}
|
|
17420
17595
|
sendRtcSignal(tunnel, signal) {
|
|
17421
17596
|
if (signal.type === "answer") {
|
|
17422
17597
|
this.sendControl("signal.answer", {
|
|
@@ -17434,6 +17609,20 @@ var HostServerConnection = class {
|
|
|
17434
17609
|
}
|
|
17435
17610
|
handleRtcOpened(tunnel, selected) {
|
|
17436
17611
|
if (this.tunnels.get(tunnel.connectionId) !== tunnel || tunnel.rtc === void 0) return;
|
|
17612
|
+
const requiredCapability = selected === "turn" ? "transport.turn" : "transport.p2p";
|
|
17613
|
+
if (!this.negotiatedCapabilities.includes(requiredCapability)) {
|
|
17614
|
+
const error = new Error(`WebRTC selected unnegotiated transport: ${selected}`);
|
|
17615
|
+
if (!this.negotiatedCapabilities.includes("transport.relay")) {
|
|
17616
|
+
void this.dropTunnel(tunnel.connectionId, "CONNECTION_FAILED");
|
|
17617
|
+
return;
|
|
17618
|
+
}
|
|
17619
|
+
void this.handleRtcFailed(
|
|
17620
|
+
tunnel,
|
|
17621
|
+
tunnel.rtc,
|
|
17622
|
+
error
|
|
17623
|
+
);
|
|
17624
|
+
return;
|
|
17625
|
+
}
|
|
17437
17626
|
tunnel.transport = selected;
|
|
17438
17627
|
tunnel.transportMode = tunnel.rtc.selectedPathMode();
|
|
17439
17628
|
this.sendTransportSelected(tunnel, selected);
|
|
@@ -17443,6 +17632,13 @@ var HostServerConnection = class {
|
|
|
17443
17632
|
transport: tunnel.transportMode ?? selected,
|
|
17444
17633
|
...webrtcDiagnosticsLogFields2(rtcDiagnostics(tunnel.rtc))
|
|
17445
17634
|
});
|
|
17635
|
+
void this.resumePendingHandshake(tunnel).catch((error) => {
|
|
17636
|
+
this.logger.warn("pending secure handshake failed", {
|
|
17637
|
+
connectionId: shortId3(tunnel.connectionId),
|
|
17638
|
+
reason: diagnosticReason3(error)
|
|
17639
|
+
});
|
|
17640
|
+
void this.dropTunnel(tunnel.connectionId, "SECURE_CHANNEL_FAILED");
|
|
17641
|
+
});
|
|
17446
17642
|
}
|
|
17447
17643
|
async handleRtcFailed(tunnel, rtc, error) {
|
|
17448
17644
|
if (this.tunnels.get(tunnel.connectionId) !== tunnel || tunnel.rtc !== rtc) return;
|
|
@@ -17463,6 +17659,7 @@ var HostServerConnection = class {
|
|
|
17463
17659
|
reason: diagnosticReason3(error),
|
|
17464
17660
|
...webrtcDiagnosticsLogFields2(rtcDiagnostics(rtc))
|
|
17465
17661
|
});
|
|
17662
|
+
await this.resumePendingHandshake(tunnel);
|
|
17466
17663
|
}
|
|
17467
17664
|
sendTransportSelected(tunnel, transport) {
|
|
17468
17665
|
this.sendControl("transport.selected", {
|
|
@@ -17662,6 +17859,13 @@ function requireSignalIce(value) {
|
|
|
17662
17859
|
}
|
|
17663
17860
|
return payload;
|
|
17664
17861
|
}
|
|
17862
|
+
function requireTransportSelected(value) {
|
|
17863
|
+
const payload = requireObject(value);
|
|
17864
|
+
if (typeof payload.connectionId !== "string" || typeof payload.targetDeviceId !== "string" || payload.transport !== "p2p" && payload.transport !== "turn" && payload.transport !== "relay") {
|
|
17865
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "transport.selected payload is invalid.");
|
|
17866
|
+
}
|
|
17867
|
+
return payload;
|
|
17868
|
+
}
|
|
17665
17869
|
function requireControlError(value) {
|
|
17666
17870
|
const payload = requireObject(value);
|
|
17667
17871
|
if (typeof payload.code !== "string" || typeof payload.message !== "string" || payload.connectionId !== void 0 && (typeof payload.connectionId !== "string" || payload.connectionId === "")) {
|
|
@@ -19249,7 +19453,7 @@ export {
|
|
|
19249
19453
|
PluginControlRuntime,
|
|
19250
19454
|
RemoteFileViewerBridge,
|
|
19251
19455
|
RemoteHarnessApiProxy,
|
|
19252
|
-
RemoteTypertGateway,
|
|
19456
|
+
RemoteTypertGateway2 as RemoteTypertGateway,
|
|
19253
19457
|
RpcError,
|
|
19254
19458
|
RpcRouter,
|
|
19255
19459
|
ServerApiError,
|