ds-harness-remote 0.3.16 → 0.3.18
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 +29 -10
- package/dist/client-runtime.d.ts +12 -1
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +119 -9
- package/dist/client-runtime.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.github.js +231 -9
- package/dist/client.js +231 -9
- package/dist/client.js.map +1 -1
- package/dist/file-viewer-bridge.d.ts +30 -0
- package/dist/file-viewer-bridge.d.ts.map +1 -0
- package/dist/file-viewer-bridge.js +128 -0
- package/dist/file-viewer-bridge.js.map +1 -0
- package/dist/file-viewer-contract.d.ts +5 -0
- package/dist/file-viewer-contract.d.ts.map +1 -0
- package/dist/file-viewer-contract.js +4 -0
- package/dist/file-viewer-contract.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +669 -263
- package/dist/index.js.map +4 -4
- package/dist/remote-file-content-provider.d.ts +36 -0
- package/dist/remote-file-content-provider.d.ts.map +1 -0
- package/dist/remote-file-content-provider.js +73 -0
- package/dist/remote-file-content-provider.js.map +1 -0
- package/dist/rpc-router.d.ts +4 -2
- package/dist/rpc-router.d.ts.map +1 -1
- package/dist/rpc-router.js +11 -2
- package/dist/rpc-router.js.map +1 -1
- package/dist/server-connection.d.ts +2 -1
- package/dist/server-connection.d.ts.map +1 -1
- package/dist/server-connection.js +9 -6
- package/dist/server-connection.js.map +1 -1
- package/dist/service.d.ts +3 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +9 -3
- package/dist/service.js.map +1 -1
- package/dist/typert-gateway-switch.d.ts +6 -1
- package/dist/typert-gateway-switch.d.ts.map +1 -1
- package/dist/typert-gateway-switch.js +19 -4
- 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
|
@@ -4083,7 +4083,8 @@ var rpcMethods = [
|
|
|
4083
4083
|
"harness.api.call",
|
|
4084
4084
|
"harness.api.respond",
|
|
4085
4085
|
"harness.api.stream.open",
|
|
4086
|
-
"harness.api.stream.close"
|
|
4086
|
+
"harness.api.stream.close",
|
|
4087
|
+
"fileviewer.call"
|
|
4087
4088
|
];
|
|
4088
4089
|
var rpcMethodSchema = external_exports.enum(rpcMethods);
|
|
4089
4090
|
var messageTypeSchema = external_exports.enum(messageTypes);
|
|
@@ -4803,6 +4804,7 @@ var RtcDataChannelTransport = class {
|
|
|
4803
4804
|
bytesSent = 0;
|
|
4804
4805
|
bytesReceived = 0;
|
|
4805
4806
|
selected;
|
|
4807
|
+
selectedMode;
|
|
4806
4808
|
lastBufferedAmount = 0;
|
|
4807
4809
|
constructor(options) {
|
|
4808
4810
|
this.role = options.role;
|
|
@@ -4879,10 +4881,27 @@ var RtcDataChannelTransport = class {
|
|
|
4879
4881
|
selectedTransport() {
|
|
4880
4882
|
return this.selected;
|
|
4881
4883
|
}
|
|
4884
|
+
selectedPathMode() {
|
|
4885
|
+
return this.selectedMode;
|
|
4886
|
+
}
|
|
4887
|
+
async connectionDetails() {
|
|
4888
|
+
let statsDetails = {};
|
|
4889
|
+
try {
|
|
4890
|
+
statsDetails = inspectSelectedPath(await this.pc.getStats());
|
|
4891
|
+
} catch {
|
|
4892
|
+
}
|
|
4893
|
+
return {
|
|
4894
|
+
...statsDetails,
|
|
4895
|
+
mode: statsDetails.mode ?? this.selectedMode,
|
|
4896
|
+
connectionState: this.pc.connectionState,
|
|
4897
|
+
iceConnectionState: this.pc.iceConnectionState,
|
|
4898
|
+
dataChannelState: this.channel?.readyState
|
|
4899
|
+
};
|
|
4900
|
+
}
|
|
4882
4901
|
getStats() {
|
|
4883
4902
|
const connected = this.channel?.readyState === "open";
|
|
4884
4903
|
return {
|
|
4885
|
-
mode: !connected ? "Disconnected" : this.selected === "turn" ? "TURN" : "P2P",
|
|
4904
|
+
mode: !connected ? "Disconnected" : this.selectedMode ?? (this.selected === "turn" ? "TURN" : "P2P"),
|
|
4886
4905
|
connected,
|
|
4887
4906
|
bytesSent: this.bytesSent,
|
|
4888
4907
|
bytesReceived: this.bytesReceived
|
|
@@ -5043,9 +5062,12 @@ var RtcDataChannelTransport = class {
|
|
|
5043
5062
|
}
|
|
5044
5063
|
async resolveSelectedTransport() {
|
|
5045
5064
|
try {
|
|
5046
|
-
|
|
5065
|
+
const selected = inspectSelectedPath(await this.pc.getStats());
|
|
5066
|
+
this.selected = selected?.transport;
|
|
5067
|
+
this.selectedMode = selected?.mode;
|
|
5047
5068
|
} catch {
|
|
5048
5069
|
this.selected = void 0;
|
|
5070
|
+
this.selectedMode = void 0;
|
|
5049
5071
|
}
|
|
5050
5072
|
}
|
|
5051
5073
|
checkConnectionState() {
|
|
@@ -5119,28 +5141,73 @@ var RtcConnectError = class extends Error {
|
|
|
5119
5141
|
this.code = code;
|
|
5120
5142
|
}
|
|
5121
5143
|
};
|
|
5122
|
-
function
|
|
5123
|
-
const
|
|
5144
|
+
function inspectSelectedPath(stats) {
|
|
5145
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
5146
|
+
const candidatePairs = /* @__PURE__ */ new Map();
|
|
5124
5147
|
const selectedPairs = [];
|
|
5125
|
-
|
|
5148
|
+
const selectedPairIds = /* @__PURE__ */ new Set();
|
|
5149
|
+
for (const [reportId, entry] of stats) {
|
|
5126
5150
|
if (entry.type === "local-candidate" || entry.type === "remote-candidate") {
|
|
5127
5151
|
if (typeof entry.candidateType === "string" && entry.id !== void 0) {
|
|
5128
|
-
|
|
5152
|
+
candidates.set(String(entry.id), entry);
|
|
5129
5153
|
}
|
|
5130
|
-
} else if (entry.type === "candidate-pair"
|
|
5154
|
+
} else if (entry.type === "candidate-pair") {
|
|
5155
|
+
candidatePairs.set(String(entry.id ?? reportId), entry);
|
|
5131
5156
|
if (entry.selected === true || entry.nominated === true)
|
|
5132
5157
|
selectedPairs.push(entry);
|
|
5133
|
-
}
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5158
|
+
} else if (entry.type === "transport" && typeof entry.selectedCandidatePairId === "string") {
|
|
5159
|
+
selectedPairIds.add(entry.selectedCandidatePairId);
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
const pairs = [
|
|
5163
|
+
...[...selectedPairIds].map((id) => candidatePairs.get(id)).filter((pair) => pair !== void 0),
|
|
5164
|
+
...selectedPairs
|
|
5165
|
+
];
|
|
5166
|
+
for (const pair of pairs) {
|
|
5167
|
+
const local = candidates.get(String(pair.localCandidateId));
|
|
5168
|
+
const remote = candidates.get(String(pair.remoteCandidateId));
|
|
5169
|
+
const localType = local?.candidateType;
|
|
5170
|
+
const remoteType = remote?.candidateType;
|
|
5171
|
+
let selected;
|
|
5172
|
+
if (localType === "relay" || remoteType === "relay")
|
|
5173
|
+
selected = { transport: "turn", mode: "TURN" };
|
|
5174
|
+
if (local === void 0 && remote === void 0)
|
|
5175
|
+
continue;
|
|
5176
|
+
selected ??= {
|
|
5177
|
+
transport: "p2p",
|
|
5178
|
+
mode: localType === "host" && remoteType === "host" ? "LAN" : "P2P"
|
|
5179
|
+
};
|
|
5180
|
+
const currentRoundTripTime = numberStat(pair, "currentRoundTripTime");
|
|
5181
|
+
return {
|
|
5182
|
+
...selected,
|
|
5183
|
+
localCandidateType: stringStat(local, "candidateType"),
|
|
5184
|
+
remoteCandidateType: stringStat(remote, "candidateType"),
|
|
5185
|
+
localAddress: candidateAddress(local),
|
|
5186
|
+
remoteAddress: candidateAddress(remote),
|
|
5187
|
+
protocol: stringStat(local, "protocol") ?? stringStat(remote, "protocol"),
|
|
5188
|
+
relayProtocol: stringStat(local, "relayProtocol") ?? stringStat(remote, "relayProtocol"),
|
|
5189
|
+
currentRoundTripTimeMs: currentRoundTripTime === void 0 ? void 0 : Math.round(currentRoundTripTime * 1e3),
|
|
5190
|
+
availableOutgoingBitrate: numberStat(pair, "availableOutgoingBitrate"),
|
|
5191
|
+
bytesSent: numberStat(pair, "bytesSent"),
|
|
5192
|
+
bytesReceived: numberStat(pair, "bytesReceived")
|
|
5193
|
+
};
|
|
5142
5194
|
}
|
|
5143
|
-
return
|
|
5195
|
+
return {};
|
|
5196
|
+
}
|
|
5197
|
+
function candidateAddress(candidate) {
|
|
5198
|
+
const address = stringStat(candidate, "address") ?? stringStat(candidate, "ip");
|
|
5199
|
+
if (address === void 0)
|
|
5200
|
+
return void 0;
|
|
5201
|
+
const port = numberStat(candidate, "port");
|
|
5202
|
+
return port === void 0 ? address : `${address}:${port}`;
|
|
5203
|
+
}
|
|
5204
|
+
function stringStat(entry, key) {
|
|
5205
|
+
const value = entry?.[key];
|
|
5206
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
5207
|
+
}
|
|
5208
|
+
function numberStat(entry, key) {
|
|
5209
|
+
const value = entry?.[key];
|
|
5210
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5144
5211
|
}
|
|
5145
5212
|
function requireSdp(description) {
|
|
5146
5213
|
if (typeof description.sdp !== "string" || description.sdp.length === 0) {
|
|
@@ -5159,7 +5226,7 @@ function asError(error) {
|
|
|
5159
5226
|
|
|
5160
5227
|
// ../webrtc/dist/adaptive-transport.js
|
|
5161
5228
|
var DEFAULT_CAPABILITIES = ["transport.p2p", "transport.turn", "transport.relay", "harness.api.v1"];
|
|
5162
|
-
var DEFAULT_PREFERRED_TRANSPORTS = ["p2p", "turn", "relay"];
|
|
5229
|
+
var DEFAULT_PREFERRED_TRANSPORTS = ["lan", "p2p", "turn", "relay"];
|
|
5163
5230
|
var AdaptiveTransport = class extends BaseTransport {
|
|
5164
5231
|
url;
|
|
5165
5232
|
options;
|
|
@@ -5177,6 +5244,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5177
5244
|
handshakeTimer;
|
|
5178
5245
|
webrtcEnabled = true;
|
|
5179
5246
|
serverNegotiateTimeoutMs;
|
|
5247
|
+
connectedAt;
|
|
5180
5248
|
constructor(url, options) {
|
|
5181
5249
|
super();
|
|
5182
5250
|
this.url = url;
|
|
@@ -5246,6 +5314,16 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5246
5314
|
remoteDeviceId: this.options.targetDeviceId
|
|
5247
5315
|
};
|
|
5248
5316
|
}
|
|
5317
|
+
async connectionDetails() {
|
|
5318
|
+
return {
|
|
5319
|
+
...this.connectionId === void 0 ? {} : { connectionId: this.connectionId },
|
|
5320
|
+
...this.connectedAt === void 0 ? {} : { connectedAt: this.connectedAt },
|
|
5321
|
+
controlChannelUrl: this.url,
|
|
5322
|
+
controlChannelState: socketState(this.socket?.readyState),
|
|
5323
|
+
preferredTransports: this.options.forceRelay === true ? ["relay"] : [...this.options.preferredTransports ?? DEFAULT_PREFERRED_TRANSPORTS],
|
|
5324
|
+
...this.rtc === void 0 ? {} : { webRtc: await this.rtc.connectionDetails() }
|
|
5325
|
+
};
|
|
5326
|
+
}
|
|
5249
5327
|
async sendHandshake(step, data) {
|
|
5250
5328
|
if (this.socket?.readyState !== WebSocket.OPEN || this.connectionId === void 0) {
|
|
5251
5329
|
throw new Error("adaptive transport has not been authorized");
|
|
@@ -5276,10 +5354,9 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5276
5354
|
let mode = "Disconnected";
|
|
5277
5355
|
if (this.selected === "relay")
|
|
5278
5356
|
mode = relayConnected ? "Relay" : "Disconnected";
|
|
5279
|
-
else if (this.selected === "turn")
|
|
5280
|
-
mode = webrtcConnected ?
|
|
5281
|
-
|
|
5282
|
-
mode = webrtcConnected ? "P2P" : "Disconnected";
|
|
5357
|
+
else if (this.selected === "turn" || this.selected === "p2p") {
|
|
5358
|
+
mode = webrtcConnected ? this.rtc.getStats().mode : "Disconnected";
|
|
5359
|
+
}
|
|
5283
5360
|
return { mode, connected, bytesSent: this.bytesSent, bytesReceived: this.bytesReceived };
|
|
5284
5361
|
}
|
|
5285
5362
|
async handleSocketMessage(raw) {
|
|
@@ -5365,14 +5442,19 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5365
5442
|
if (this.connectionId === void 0)
|
|
5366
5443
|
return;
|
|
5367
5444
|
const preferred = this.options.preferredTransports ?? [...DEFAULT_PREFERRED_TRANSPORTS];
|
|
5368
|
-
const wantWebRtc = !this.options.forceRelay && this.webrtcEnabled && (preferred.includes("p2p") || preferred.includes("turn"));
|
|
5445
|
+
const wantWebRtc = !this.options.forceRelay && this.webrtcEnabled && (preferred.includes("lan") || preferred.includes("p2p") || preferred.includes("turn"));
|
|
5369
5446
|
let selected = "relay";
|
|
5370
5447
|
if (wantWebRtc) {
|
|
5371
5448
|
try {
|
|
5372
5449
|
const rtcSelected = await this.tryWebRtc();
|
|
5373
5450
|
this.dataMode = "webrtc";
|
|
5374
5451
|
selected = rtcSelected;
|
|
5375
|
-
} catch {
|
|
5452
|
+
} catch (error) {
|
|
5453
|
+
const reason = error instanceof Error ? error : new Error("WebRTC negotiation failed.");
|
|
5454
|
+
try {
|
|
5455
|
+
this.options.onWebRtcFallback?.(reason);
|
|
5456
|
+
} catch {
|
|
5457
|
+
}
|
|
5376
5458
|
await this.rtc?.close();
|
|
5377
5459
|
this.rtc = void 0;
|
|
5378
5460
|
this.dataMode = "relay";
|
|
@@ -5457,6 +5539,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5457
5539
|
}
|
|
5458
5540
|
finishConnection() {
|
|
5459
5541
|
this.clearHandshake();
|
|
5542
|
+
this.connectedAt ??= Date.now();
|
|
5460
5543
|
this.readyResolve?.();
|
|
5461
5544
|
this.readyResolve = void 0;
|
|
5462
5545
|
this.readyReject = void 0;
|
|
@@ -5487,6 +5570,15 @@ function adaptiveControlCloseError(event) {
|
|
|
5487
5570
|
function isObject(value) {
|
|
5488
5571
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5489
5572
|
}
|
|
5573
|
+
function socketState(readyState) {
|
|
5574
|
+
if (readyState === 0)
|
|
5575
|
+
return "connecting";
|
|
5576
|
+
if (readyState === 1)
|
|
5577
|
+
return "open";
|
|
5578
|
+
if (readyState === 2)
|
|
5579
|
+
return "closing";
|
|
5580
|
+
return "closed";
|
|
5581
|
+
}
|
|
5490
5582
|
|
|
5491
5583
|
// src/api-proxy-switch.ts
|
|
5492
5584
|
var SWITCHED_DOMAINS = [
|
|
@@ -12973,7 +13065,7 @@ function normalizeServerUrl(value) {
|
|
|
12973
13065
|
}
|
|
12974
13066
|
|
|
12975
13067
|
// src/version.ts
|
|
12976
|
-
var PLUGIN_VERSION = "0.3.
|
|
13068
|
+
var PLUGIN_VERSION = "0.3.18";
|
|
12977
13069
|
|
|
12978
13070
|
// src/server-api.ts
|
|
12979
13071
|
var HostServerApi = class {
|
|
@@ -13359,6 +13451,7 @@ function requireRecord(value, name2) {
|
|
|
13359
13451
|
|
|
13360
13452
|
// src/typert-gateway-switch.ts
|
|
13361
13453
|
var REMOTE_COMMAND_METHODS = ["execute", "list"];
|
|
13454
|
+
var ALL_REMOTE_COMMANDS = { execute: true, list: true };
|
|
13362
13455
|
var TypertGatewaySwitch = class {
|
|
13363
13456
|
constructor(gateway) {
|
|
13364
13457
|
this.gateway = gateway;
|
|
@@ -13368,6 +13461,7 @@ var TypertGatewaySwitch = class {
|
|
|
13368
13461
|
originalInvoke;
|
|
13369
13462
|
localInvoke;
|
|
13370
13463
|
remoteInvoke;
|
|
13464
|
+
remoteSupport = { execute: false, list: false };
|
|
13371
13465
|
installed = false;
|
|
13372
13466
|
/** A facade that always invokes the original local gateway. */
|
|
13373
13467
|
local() {
|
|
@@ -13375,15 +13469,24 @@ var TypertGatewaySwitch = class {
|
|
|
13375
13469
|
}
|
|
13376
13470
|
install() {
|
|
13377
13471
|
if (this.installed) return;
|
|
13378
|
-
this.gateway.invoke = (request) =>
|
|
13472
|
+
this.gateway.invoke = (request) => {
|
|
13473
|
+
if (request.namespace !== "commands" || !isRemoteCommandMethod(request.method) || this.remoteInvoke === void 0) {
|
|
13474
|
+
return this.localInvoke(request);
|
|
13475
|
+
}
|
|
13476
|
+
if (this.remoteSupport[request.method]) return this.remoteInvoke(request);
|
|
13477
|
+
if (request.method === "list") return Promise.resolve([]);
|
|
13478
|
+
return this.localInvoke(request);
|
|
13479
|
+
};
|
|
13379
13480
|
this.installed = true;
|
|
13380
13481
|
}
|
|
13381
|
-
selectRemote(invoke) {
|
|
13482
|
+
selectRemote(invoke, support = ALL_REMOTE_COMMANDS) {
|
|
13382
13483
|
if (!this.installed) throw new Error("The Typert gateway switch is not installed.");
|
|
13383
13484
|
this.remoteInvoke = invoke;
|
|
13485
|
+
this.remoteSupport = { ...support };
|
|
13384
13486
|
}
|
|
13385
13487
|
selectLocal() {
|
|
13386
13488
|
this.remoteInvoke = void 0;
|
|
13489
|
+
this.remoteSupport = { execute: false, list: false };
|
|
13387
13490
|
}
|
|
13388
13491
|
restore() {
|
|
13389
13492
|
if (!this.installed) return;
|
|
@@ -13396,14 +13499,210 @@ function isRemoteCommandMethod(method) {
|
|
|
13396
13499
|
return REMOTE_COMMAND_METHODS.includes(method);
|
|
13397
13500
|
}
|
|
13398
13501
|
|
|
13502
|
+
// src/werift-rtc.ts
|
|
13503
|
+
import { networkInterfaces } from "node:os";
|
|
13504
|
+
var cachedFactory;
|
|
13505
|
+
async function loadWeriftFactory() {
|
|
13506
|
+
if (cachedFactory !== void 0) return cachedFactory;
|
|
13507
|
+
try {
|
|
13508
|
+
const werift = await import("werift");
|
|
13509
|
+
cachedFactory = buildWeriftFactory(werift);
|
|
13510
|
+
return cachedFactory;
|
|
13511
|
+
} catch {
|
|
13512
|
+
return void 0;
|
|
13513
|
+
}
|
|
13514
|
+
}
|
|
13515
|
+
function buildWeriftFactory(werift) {
|
|
13516
|
+
return {
|
|
13517
|
+
create(configuration) {
|
|
13518
|
+
const hostIpv4 = detectHostIpv4();
|
|
13519
|
+
const raw = new werift.RTCPeerConnection({
|
|
13520
|
+
iceServers: configuration.iceServers ?? [],
|
|
13521
|
+
iceUseIpv6: false,
|
|
13522
|
+
iceUseLinkLocalAddress: false,
|
|
13523
|
+
...hostIpv4 === void 0 ? {} : { iceInterfaceAddresses: { udp4: hostIpv4 } }
|
|
13524
|
+
});
|
|
13525
|
+
let onIceCandidate = null;
|
|
13526
|
+
let onDataChannel = null;
|
|
13527
|
+
raw.onicecandidate = (event) => {
|
|
13528
|
+
if (onIceCandidate === null) return;
|
|
13529
|
+
onIceCandidate({ candidate: event.candidate === void 0 ? null : event.candidate.toJSON() });
|
|
13530
|
+
};
|
|
13531
|
+
raw.ondatachannel = (event) => {
|
|
13532
|
+
if (onDataChannel === null) return;
|
|
13533
|
+
onDataChannel({ channel: adaptDataChannel2(event.channel) });
|
|
13534
|
+
};
|
|
13535
|
+
const pc = {
|
|
13536
|
+
get connectionState() {
|
|
13537
|
+
return raw.connectionState;
|
|
13538
|
+
},
|
|
13539
|
+
get iceConnectionState() {
|
|
13540
|
+
return raw.iceConnectionState;
|
|
13541
|
+
},
|
|
13542
|
+
get iceGatheringState() {
|
|
13543
|
+
return raw.iceGatheringState;
|
|
13544
|
+
},
|
|
13545
|
+
get signalingState() {
|
|
13546
|
+
return raw.signalingState;
|
|
13547
|
+
},
|
|
13548
|
+
set onconnectionstatechange(value) {
|
|
13549
|
+
raw.onconnectionstatechange = value;
|
|
13550
|
+
},
|
|
13551
|
+
get onconnectionstatechange() {
|
|
13552
|
+
return raw.onconnectionstatechange;
|
|
13553
|
+
},
|
|
13554
|
+
set oniceconnectionstatechange(value) {
|
|
13555
|
+
raw.oniceconnectionstatechange = value;
|
|
13556
|
+
},
|
|
13557
|
+
get oniceconnectionstatechange() {
|
|
13558
|
+
return raw.oniceconnectionstatechange;
|
|
13559
|
+
},
|
|
13560
|
+
set onicegatheringstatechange(value) {
|
|
13561
|
+
raw.onicegatheringstatechange = value;
|
|
13562
|
+
},
|
|
13563
|
+
get onicegatheringstatechange() {
|
|
13564
|
+
return raw.onicegatheringstatechange;
|
|
13565
|
+
},
|
|
13566
|
+
set onicecandidate(value) {
|
|
13567
|
+
onIceCandidate = value;
|
|
13568
|
+
},
|
|
13569
|
+
get onicecandidate() {
|
|
13570
|
+
return onIceCandidate;
|
|
13571
|
+
},
|
|
13572
|
+
set ondatachannel(value) {
|
|
13573
|
+
onDataChannel = value;
|
|
13574
|
+
},
|
|
13575
|
+
get ondatachannel() {
|
|
13576
|
+
return onDataChannel;
|
|
13577
|
+
},
|
|
13578
|
+
createDataChannel(label, options) {
|
|
13579
|
+
return adaptDataChannel2(raw.createDataChannel(label, { ordered: options?.ordered ?? true }));
|
|
13580
|
+
},
|
|
13581
|
+
createOffer() {
|
|
13582
|
+
return raw.createOffer();
|
|
13583
|
+
},
|
|
13584
|
+
createAnswer() {
|
|
13585
|
+
return raw.createAnswer();
|
|
13586
|
+
},
|
|
13587
|
+
setLocalDescription(description) {
|
|
13588
|
+
return raw.setLocalDescription(description).then(() => void 0);
|
|
13589
|
+
},
|
|
13590
|
+
setRemoteDescription(description) {
|
|
13591
|
+
return raw.setRemoteDescription(description);
|
|
13592
|
+
},
|
|
13593
|
+
addIceCandidate(candidate) {
|
|
13594
|
+
return raw.addIceCandidate(candidate);
|
|
13595
|
+
},
|
|
13596
|
+
getStats() {
|
|
13597
|
+
return raw.getStats();
|
|
13598
|
+
},
|
|
13599
|
+
close() {
|
|
13600
|
+
void raw.close().catch(() => void 0);
|
|
13601
|
+
}
|
|
13602
|
+
};
|
|
13603
|
+
return pc;
|
|
13604
|
+
}
|
|
13605
|
+
};
|
|
13606
|
+
}
|
|
13607
|
+
function adaptDataChannel2(raw) {
|
|
13608
|
+
return {
|
|
13609
|
+
get label() {
|
|
13610
|
+
return raw.label;
|
|
13611
|
+
},
|
|
13612
|
+
get ordered() {
|
|
13613
|
+
return raw.ordered;
|
|
13614
|
+
},
|
|
13615
|
+
get readyState() {
|
|
13616
|
+
return raw.readyState;
|
|
13617
|
+
},
|
|
13618
|
+
get bufferedAmount() {
|
|
13619
|
+
return raw.bufferedAmount;
|
|
13620
|
+
},
|
|
13621
|
+
binaryType: "arraybuffer",
|
|
13622
|
+
set onopen(value) {
|
|
13623
|
+
raw.onopen = value;
|
|
13624
|
+
},
|
|
13625
|
+
get onopen() {
|
|
13626
|
+
return raw.onopen;
|
|
13627
|
+
},
|
|
13628
|
+
set onmessage(value) {
|
|
13629
|
+
raw.onmessage = value === null ? null : (event) => value({ data: toArrayBuffer2(event.data) });
|
|
13630
|
+
},
|
|
13631
|
+
get onmessage() {
|
|
13632
|
+
return null;
|
|
13633
|
+
},
|
|
13634
|
+
set onclose(value) {
|
|
13635
|
+
raw.onclose = value;
|
|
13636
|
+
},
|
|
13637
|
+
get onclose() {
|
|
13638
|
+
return raw.onclose;
|
|
13639
|
+
},
|
|
13640
|
+
set onerror(value) {
|
|
13641
|
+
raw.onerror = value;
|
|
13642
|
+
},
|
|
13643
|
+
get onerror() {
|
|
13644
|
+
return raw.onerror;
|
|
13645
|
+
},
|
|
13646
|
+
onbufferedamountlow: null,
|
|
13647
|
+
send(data) {
|
|
13648
|
+
const bytes = typeof data === "string" ? Buffer.byteLength(data) : data.byteLength;
|
|
13649
|
+
try {
|
|
13650
|
+
raw.send(typeof data === "string" ? data : Buffer.from(data));
|
|
13651
|
+
} catch (error) {
|
|
13652
|
+
console.error("[werift-send-error] bytes=" + bytes, error instanceof Error ? error.message : error);
|
|
13653
|
+
throw error;
|
|
13654
|
+
}
|
|
13655
|
+
},
|
|
13656
|
+
close() {
|
|
13657
|
+
raw.close();
|
|
13658
|
+
}
|
|
13659
|
+
};
|
|
13660
|
+
}
|
|
13661
|
+
function toArrayBuffer2(data) {
|
|
13662
|
+
if (typeof data === "string") return data;
|
|
13663
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
13664
|
+
}
|
|
13665
|
+
function detectHostIpv4() {
|
|
13666
|
+
const preferred = [];
|
|
13667
|
+
const fallback = [];
|
|
13668
|
+
for (const [name2, addresses] of Object.entries(networkInterfaces())) {
|
|
13669
|
+
if (isVirtualInterface(name2)) continue;
|
|
13670
|
+
for (const address of addresses ?? []) {
|
|
13671
|
+
if (address.internal || address.family !== "IPv4") continue;
|
|
13672
|
+
const ip = address.address;
|
|
13673
|
+
if (ip.startsWith("127.") || ip.startsWith("169.254.") || isCgnat(ip)) continue;
|
|
13674
|
+
if (isPrivate(ip)) preferred.push(ip);
|
|
13675
|
+
else fallback.push(ip);
|
|
13676
|
+
}
|
|
13677
|
+
}
|
|
13678
|
+
return preferred[0] ?? fallback[0];
|
|
13679
|
+
}
|
|
13680
|
+
function isVirtualInterface(name2) {
|
|
13681
|
+
return /^(utun|ppp|bridge|awdl|llw|gif|stf|anpi|ap\d|en[1-9]\d*$)/i.test(name2);
|
|
13682
|
+
}
|
|
13683
|
+
function isCgnat(ip) {
|
|
13684
|
+
const parts = ip.split(".").map(Number);
|
|
13685
|
+
return parts.length === 4 && parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127;
|
|
13686
|
+
}
|
|
13687
|
+
function isPrivate(ip) {
|
|
13688
|
+
const parts = ip.split(".").map(Number);
|
|
13689
|
+
if (parts.length !== 4) return false;
|
|
13690
|
+
if (parts[0] === 10) return true;
|
|
13691
|
+
if (parts[0] === 192 && parts[1] === 168) return true;
|
|
13692
|
+
return parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31;
|
|
13693
|
+
}
|
|
13694
|
+
|
|
13399
13695
|
// src/client-runtime.ts
|
|
13696
|
+
var REMOTE_COMMAND_LIST_MIN_VERSION = [0, 3, 16];
|
|
13697
|
+
var REMOTE_FILE_VIEWER_MIN_VERSION = [0, 3, 17];
|
|
13400
13698
|
var ClientModeRuntime = class {
|
|
13401
|
-
constructor(config, identities, server, apiProxy, typertGateway, logger, host) {
|
|
13699
|
+
constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadWeriftFactory) {
|
|
13402
13700
|
this.config = config;
|
|
13403
13701
|
this.identities = identities;
|
|
13404
13702
|
this.server = server;
|
|
13405
13703
|
this.logger = logger;
|
|
13406
13704
|
this.host = host;
|
|
13705
|
+
this.rtcFactoryProvider = rtcFactoryProvider;
|
|
13407
13706
|
this.proxySwitch = new ApiProxySwitch(apiProxy);
|
|
13408
13707
|
this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
|
|
13409
13708
|
}
|
|
@@ -13437,10 +13736,33 @@ var ClientModeRuntime = class {
|
|
|
13437
13736
|
...this.proxySwitch.status(),
|
|
13438
13737
|
connected: this.connected !== void 0,
|
|
13439
13738
|
transport: this.connected?.client.getStats().mode ?? "Disconnected",
|
|
13739
|
+
remoteFeatures: this.connected?.features ?? remoteHostFeatures(),
|
|
13440
13740
|
hostAuthorizationAvailable: this.host !== void 0,
|
|
13441
13741
|
...this.host === void 0 ? {} : { host: this.host.hostStatus() }
|
|
13442
13742
|
};
|
|
13443
13743
|
}
|
|
13744
|
+
async detailedStatus() {
|
|
13745
|
+
const connected = this.connected;
|
|
13746
|
+
if (connected === void 0 || this.identity === void 0) return this.status();
|
|
13747
|
+
const details = await connected.transport.connectionDetails();
|
|
13748
|
+
if (this.connected !== connected) return this.status();
|
|
13749
|
+
return {
|
|
13750
|
+
...this.status(),
|
|
13751
|
+
network: {
|
|
13752
|
+
...details,
|
|
13753
|
+
local: {
|
|
13754
|
+
deviceId: this.identity.deviceId,
|
|
13755
|
+
name: this.identity.name,
|
|
13756
|
+
platform: process.platform
|
|
13757
|
+
},
|
|
13758
|
+
remote: {
|
|
13759
|
+
deviceId: connected.target.deviceId,
|
|
13760
|
+
name: connected.target.name,
|
|
13761
|
+
platform: connected.target.platform
|
|
13762
|
+
}
|
|
13763
|
+
}
|
|
13764
|
+
};
|
|
13765
|
+
}
|
|
13444
13766
|
async devices() {
|
|
13445
13767
|
this.requireIdentity();
|
|
13446
13768
|
const serverDevices = await this.server.listDevices();
|
|
@@ -13518,7 +13840,7 @@ var ClientModeRuntime = class {
|
|
|
13518
13840
|
this.connected = next;
|
|
13519
13841
|
const remoteApi = new RemoteHarnessApiProxy(next.client).api;
|
|
13520
13842
|
this.proxySwitch.selectRemote(remoteApi, { deviceId: next.target.deviceId, name: next.target.name });
|
|
13521
|
-
this.
|
|
13843
|
+
this.selectRemoteCommands(next);
|
|
13522
13844
|
await previous?.client.close().catch(() => void 0);
|
|
13523
13845
|
this.logger.info("Harness target switched", { mode: "remote", targetDeviceId: shortId(next.target.deviceId) });
|
|
13524
13846
|
return this.status();
|
|
@@ -13552,7 +13874,7 @@ var ClientModeRuntime = class {
|
|
|
13552
13874
|
});
|
|
13553
13875
|
const workspace = unwrapNativeResult(response);
|
|
13554
13876
|
this.proxySwitch.selectRemote(api, { deviceId: remote.target.deviceId, name: remote.target.name });
|
|
13555
|
-
this.
|
|
13877
|
+
this.selectRemoteCommands(remote);
|
|
13556
13878
|
this.logger.info("Remote workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
|
|
13557
13879
|
return { ...this.status(), workspace };
|
|
13558
13880
|
}
|
|
@@ -13566,6 +13888,22 @@ var ClientModeRuntime = class {
|
|
|
13566
13888
|
this.proxySwitch.restore();
|
|
13567
13889
|
this.gatewaySwitch.restore();
|
|
13568
13890
|
}
|
|
13891
|
+
async callRemoteFileViewer(endpoint, payload, signal) {
|
|
13892
|
+
const remote = this.connected;
|
|
13893
|
+
if (remote === void 0 || this.proxySwitch.status().mode !== "remote") {
|
|
13894
|
+
throw new ClientModeError("REMOTE_NOT_CONNECTED", "No Remote Host is selected.", true);
|
|
13895
|
+
}
|
|
13896
|
+
if (!remote.features.fileViewer) {
|
|
13897
|
+
throw new ClientModeError("FEATURE_NOT_SUPPORTED", "The selected Remote Host does not support remote file viewing.");
|
|
13898
|
+
}
|
|
13899
|
+
return remote.client.rpc("fileviewer.call", { endpoint, payload }, signal);
|
|
13900
|
+
}
|
|
13901
|
+
selectRemoteCommands(remote) {
|
|
13902
|
+
this.gatewaySwitch.selectRemote((request) => invokeRemoteCommand(remote.client, request), {
|
|
13903
|
+
execute: true,
|
|
13904
|
+
list: remote.features.commandList
|
|
13905
|
+
});
|
|
13906
|
+
}
|
|
13569
13907
|
async connect(targetDeviceId, signal) {
|
|
13570
13908
|
signal?.throwIfAborted();
|
|
13571
13909
|
const identity = this.requireIdentity();
|
|
@@ -13577,19 +13915,43 @@ var ClientModeRuntime = class {
|
|
|
13577
13915
|
const presence = await this.server.presenceFor(targetDeviceId);
|
|
13578
13916
|
if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
|
|
13579
13917
|
const credentials = await this.server.authenticate(identity);
|
|
13580
|
-
const
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13918
|
+
const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider().catch(() => void 0);
|
|
13919
|
+
let webRtcFallback = false;
|
|
13920
|
+
const createTransport = (relayOnly) => new AdaptiveTransport(
|
|
13921
|
+
websocketUrl(this.server.baseUrl),
|
|
13922
|
+
{
|
|
13923
|
+
role: "client",
|
|
13924
|
+
deviceId: identity.deviceId,
|
|
13925
|
+
accessToken: credentials.accessToken,
|
|
13926
|
+
targetDeviceId,
|
|
13927
|
+
forceRelay: this.config.forceRelay || relayOnly,
|
|
13928
|
+
preferredTransports: this.config.forceRelay || relayOnly ? ["relay"] : ["lan", "p2p", "turn", "relay"],
|
|
13929
|
+
...rtcFactory === void 0 || relayOnly ? {} : { rtcFactory },
|
|
13930
|
+
fetchIceServers: async (connectionId) => this.server.turnCredentials(connectionId),
|
|
13931
|
+
onWebRtcFallback: (error) => {
|
|
13932
|
+
webRtcFallback = true;
|
|
13933
|
+
this.logger.warn("remote Harness WebRTC failed; using relay", {
|
|
13934
|
+
targetDeviceId: shortId(target.deviceId),
|
|
13935
|
+
reason: diagnosticReason(error)
|
|
13936
|
+
});
|
|
13937
|
+
}
|
|
13938
|
+
}
|
|
13939
|
+
);
|
|
13940
|
+
let transport = createTransport(false);
|
|
13941
|
+
let client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 6e4);
|
|
13590
13942
|
try {
|
|
13591
13943
|
await client.connect();
|
|
13592
13944
|
signal?.throwIfAborted();
|
|
13945
|
+
if (webRtcFallback) {
|
|
13946
|
+
await client.close();
|
|
13947
|
+
transport = createTransport(true);
|
|
13948
|
+
client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 6e4);
|
|
13949
|
+
await client.connect();
|
|
13950
|
+
signal?.throwIfAborted();
|
|
13951
|
+
this.logger.info("remote Harness relay fallback re-established", {
|
|
13952
|
+
targetDeviceId: shortId(target.deviceId)
|
|
13953
|
+
});
|
|
13954
|
+
}
|
|
13593
13955
|
client.onClose(() => {
|
|
13594
13956
|
if (this.connected?.client !== client) return;
|
|
13595
13957
|
this.connected = void 0;
|
|
@@ -13600,7 +13962,11 @@ var ClientModeRuntime = class {
|
|
|
13600
13962
|
targetDeviceId: shortId(target.deviceId)
|
|
13601
13963
|
});
|
|
13602
13964
|
});
|
|
13603
|
-
|
|
13965
|
+
this.logger.info("remote Harness transport ready", {
|
|
13966
|
+
targetDeviceId: shortId(target.deviceId),
|
|
13967
|
+
transport: client.getStats().mode
|
|
13968
|
+
});
|
|
13969
|
+
return { client, target, transport, features: remoteHostFeatures(serverDevice.clientVersion) };
|
|
13604
13970
|
} catch (error) {
|
|
13605
13971
|
await client.close().catch(() => void 0);
|
|
13606
13972
|
throw error;
|
|
@@ -13616,7 +13982,7 @@ var ClientModeRuntime = class {
|
|
|
13616
13982
|
}
|
|
13617
13983
|
async handleControl(endpoint, payload, signal) {
|
|
13618
13984
|
try {
|
|
13619
|
-
if (endpoint === "status") return ok(this.
|
|
13985
|
+
if (endpoint === "status") return ok(await this.detailedStatus());
|
|
13620
13986
|
if (endpoint === "devices") return ok(await this.devices());
|
|
13621
13987
|
if (endpoint === "client.account.login") {
|
|
13622
13988
|
const value = record(payload);
|
|
@@ -13654,6 +14020,10 @@ var ClientModeRuntime = class {
|
|
|
13654
14020
|
}
|
|
13655
14021
|
return ok(await this.openRemoteWorkspace(value.targetDeviceId, value.path, signal));
|
|
13656
14022
|
}
|
|
14023
|
+
if (endpoint === "fileviewer.stat" || endpoint === "fileviewer.readRange" || endpoint === "fileviewer.list") {
|
|
14024
|
+
const method = endpoint === "fileviewer.stat" ? "stat" : endpoint === "fileviewer.readRange" ? "readRange" : "list";
|
|
14025
|
+
return ok(await this.callRemoteFileViewer(method, payload, signal));
|
|
14026
|
+
}
|
|
13657
14027
|
if (endpoint === "host.account.login") {
|
|
13658
14028
|
if (this.host === void 0) throw new ClientModeError("METHOD_NOT_ALLOWED", "This plugin is not running as a Host.");
|
|
13659
14029
|
const value = record(payload);
|
|
@@ -13776,6 +14146,29 @@ function fail(error) {
|
|
|
13776
14146
|
function shortId(value) {
|
|
13777
14147
|
return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
|
|
13778
14148
|
}
|
|
14149
|
+
function remoteHostFeatures(clientVersion) {
|
|
14150
|
+
return {
|
|
14151
|
+
commandList: isVersionAtLeast(clientVersion, REMOTE_COMMAND_LIST_MIN_VERSION),
|
|
14152
|
+
fileViewer: isVersionAtLeast(clientVersion, REMOTE_FILE_VIEWER_MIN_VERSION)
|
|
14153
|
+
};
|
|
14154
|
+
}
|
|
14155
|
+
function isVersionAtLeast(value, minimum) {
|
|
14156
|
+
const match = value?.match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
14157
|
+
if (match === void 0 || match === null) return false;
|
|
14158
|
+
const version = match.slice(1, 4).map((part) => Number(part));
|
|
14159
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
14160
|
+
const part = version[index] ?? 0;
|
|
14161
|
+
const expected = minimum[index] ?? 0;
|
|
14162
|
+
if (part > expected) return true;
|
|
14163
|
+
if (part < expected) return false;
|
|
14164
|
+
}
|
|
14165
|
+
return true;
|
|
14166
|
+
}
|
|
14167
|
+
function diagnosticReason(error) {
|
|
14168
|
+
const code = "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
14169
|
+
const message = error.message.replace(/[\r\n\t]+/g, " ").slice(0, 240);
|
|
14170
|
+
return code === void 0 ? message : `${code}: ${message}`;
|
|
14171
|
+
}
|
|
13779
14172
|
|
|
13780
14173
|
// src/control-runtime.ts
|
|
13781
14174
|
import { hostname as hostname2 } from "node:os";
|
|
@@ -14396,7 +14789,7 @@ var ConnectionController = class {
|
|
|
14396
14789
|
} catch (error) {
|
|
14397
14790
|
this.logger?.warn("peer message handling failed; disconnecting", {
|
|
14398
14791
|
peerDeviceId: shortId2(connection.channel.peerDeviceId),
|
|
14399
|
-
reason:
|
|
14792
|
+
reason: diagnosticReason2(error)
|
|
14400
14793
|
});
|
|
14401
14794
|
await this.disconnect(connection);
|
|
14402
14795
|
}
|
|
@@ -14414,7 +14807,7 @@ var ConnectionController = class {
|
|
|
14414
14807
|
this.logger?.warn("peer send failed; disconnecting", {
|
|
14415
14808
|
peerDeviceId: shortId2(connection.channel.peerDeviceId),
|
|
14416
14809
|
messageType: message.type,
|
|
14417
|
-
reason:
|
|
14810
|
+
reason: diagnosticReason2(error)
|
|
14418
14811
|
});
|
|
14419
14812
|
await this.disconnect(connection);
|
|
14420
14813
|
}
|
|
@@ -14444,7 +14837,7 @@ var ConnectionRejectedError = class extends Error {
|
|
|
14444
14837
|
this.code = code;
|
|
14445
14838
|
}
|
|
14446
14839
|
};
|
|
14447
|
-
function
|
|
14840
|
+
function diagnosticReason2(error) {
|
|
14448
14841
|
const message = error instanceof Error ? error.message : String(error);
|
|
14449
14842
|
return message.replace(/[\r\n]+/g, " ").slice(0, 160) || "Unknown peer connection failure.";
|
|
14450
14843
|
}
|
|
@@ -14458,14 +14851,16 @@ var apiMethods = /* @__PURE__ */ new Set([
|
|
|
14458
14851
|
"harness.api.call",
|
|
14459
14852
|
"harness.api.respond",
|
|
14460
14853
|
"harness.api.stream.open",
|
|
14461
|
-
"harness.api.stream.close"
|
|
14854
|
+
"harness.api.stream.close",
|
|
14855
|
+
"fileviewer.call"
|
|
14462
14856
|
]);
|
|
14463
|
-
var HOST_CAPABILITIES = ["harness.api.v1"];
|
|
14857
|
+
var HOST_CAPABILITIES = ["harness.api.v1", "fileviewer.read.v1"];
|
|
14464
14858
|
var RpcRouter = class {
|
|
14465
|
-
constructor(harnessApi, maxPending = 128, logger) {
|
|
14859
|
+
constructor(harnessApi, maxPending = 128, logger, fileViewer) {
|
|
14466
14860
|
this.harnessApi = harnessApi;
|
|
14467
14861
|
this.maxPending = maxPending;
|
|
14468
14862
|
this.logger = logger;
|
|
14863
|
+
this.fileViewer = fileViewer;
|
|
14469
14864
|
}
|
|
14470
14865
|
active = 0;
|
|
14471
14866
|
closePeerStreams() {
|
|
@@ -14500,7 +14895,7 @@ var RpcRouter = class {
|
|
|
14500
14895
|
durationMs: Math.round(performance.now() - startedAt),
|
|
14501
14896
|
code: response.payload.code,
|
|
14502
14897
|
retryable: response.payload.retryable,
|
|
14503
|
-
reason:
|
|
14898
|
+
reason: diagnosticReason3(error)
|
|
14504
14899
|
});
|
|
14505
14900
|
return response;
|
|
14506
14901
|
} finally {
|
|
@@ -14517,6 +14912,12 @@ var RpcRouter = class {
|
|
|
14517
14912
|
return this.harnessApi.openStream(params);
|
|
14518
14913
|
case "harness.api.stream.close":
|
|
14519
14914
|
return this.harnessApi.closeStream(params);
|
|
14915
|
+
case "fileviewer.call": {
|
|
14916
|
+
if (this.fileViewer === void 0) {
|
|
14917
|
+
throw new RpcError("FILE_VIEWER_UNAVAILABLE", "The Remote Host does not have DSH File Viewer available.");
|
|
14918
|
+
}
|
|
14919
|
+
return this.fileViewer.call(params);
|
|
14920
|
+
}
|
|
14520
14921
|
default:
|
|
14521
14922
|
throw new RpcError("METHOD_NOT_FOUND", "The requested method does not exist.");
|
|
14522
14923
|
}
|
|
@@ -14535,15 +14936,135 @@ function errorResponse(requestId, error) {
|
|
|
14535
14936
|
if (error instanceof external_exports.ZodError) return createRpcError(requestId, "INVALID_MESSAGE", "The RPC parameters are invalid.");
|
|
14536
14937
|
return createRpcError(requestId, "INTERNAL_ERROR", "The Host could not complete the request.");
|
|
14537
14938
|
}
|
|
14538
|
-
function
|
|
14939
|
+
function diagnosticReason3(error) {
|
|
14539
14940
|
const message = error instanceof Error ? error.message : String(error);
|
|
14540
14941
|
return message.replace(/[\r\n]+/g, " ").slice(0, 160) || "Unknown Host request failure.";
|
|
14541
14942
|
}
|
|
14542
14943
|
|
|
14944
|
+
// src/file-viewer-contract.ts
|
|
14945
|
+
var REMOTE_FILE_CHUNK_BYTES = 512 * 1024;
|
|
14946
|
+
var MAX_REMOTE_FILE_LOCATOR_CHARS = 4096;
|
|
14947
|
+
var MAX_REMOTE_DIRECTORY_ENTRIES = 1e3;
|
|
14948
|
+
|
|
14949
|
+
// src/file-viewer-bridge.ts
|
|
14950
|
+
var locatorSchema = external_exports.string().min(1).max(MAX_REMOTE_FILE_LOCATOR_CHARS);
|
|
14951
|
+
var callSchema = external_exports.discriminatedUnion("endpoint", [
|
|
14952
|
+
external_exports.object({
|
|
14953
|
+
endpoint: external_exports.literal("stat"),
|
|
14954
|
+
payload: external_exports.object({ path: locatorSchema }).strict()
|
|
14955
|
+
}).strict(),
|
|
14956
|
+
external_exports.object({
|
|
14957
|
+
endpoint: external_exports.literal("readRange"),
|
|
14958
|
+
payload: external_exports.object({
|
|
14959
|
+
path: locatorSchema,
|
|
14960
|
+
offset: external_exports.number().int().nonnegative().safe(),
|
|
14961
|
+
length: external_exports.number().int().positive().max(REMOTE_FILE_CHUNK_BYTES)
|
|
14962
|
+
}).strict()
|
|
14963
|
+
}).strict(),
|
|
14964
|
+
external_exports.object({
|
|
14965
|
+
endpoint: external_exports.literal("list"),
|
|
14966
|
+
payload: external_exports.object({ path: locatorSchema }).strict()
|
|
14967
|
+
}).strict()
|
|
14968
|
+
]);
|
|
14969
|
+
var statResultSchema = external_exports.object({
|
|
14970
|
+
path: locatorSchema,
|
|
14971
|
+
name: external_exports.string().min(1).max(1024),
|
|
14972
|
+
ext: external_exports.string().max(128),
|
|
14973
|
+
mime: external_exports.string().min(1).max(256),
|
|
14974
|
+
size: external_exports.number().int().nonnegative().safe(),
|
|
14975
|
+
mtimeMs: external_exports.number().nonnegative().optional(),
|
|
14976
|
+
isDirectory: external_exports.boolean(),
|
|
14977
|
+
exists: external_exports.boolean()
|
|
14978
|
+
}).strict();
|
|
14979
|
+
var rangeResultSchema = external_exports.object({
|
|
14980
|
+
data: external_exports.string().max(Math.ceil(REMOTE_FILE_CHUNK_BYTES / 3) * 4 + 4),
|
|
14981
|
+
offset: external_exports.number().int().nonnegative().safe(),
|
|
14982
|
+
size: external_exports.number().int().nonnegative().safe(),
|
|
14983
|
+
eof: external_exports.boolean()
|
|
14984
|
+
}).strict();
|
|
14985
|
+
var directoryEntrySchema = external_exports.object({
|
|
14986
|
+
name: external_exports.string().min(1).max(1024),
|
|
14987
|
+
path: locatorSchema,
|
|
14988
|
+
isDirectory: external_exports.boolean(),
|
|
14989
|
+
size: external_exports.number().int().nonnegative().safe().optional(),
|
|
14990
|
+
mtimeMs: external_exports.number().nonnegative().optional()
|
|
14991
|
+
}).strict();
|
|
14992
|
+
var listResultSchema = external_exports.object({
|
|
14993
|
+
path: locatorSchema,
|
|
14994
|
+
entries: external_exports.array(directoryEntrySchema).max(MAX_REMOTE_DIRECTORY_ENTRIES)
|
|
14995
|
+
}).strict();
|
|
14996
|
+
var RemoteFileViewerBridge = class {
|
|
14997
|
+
constructor(service, logger) {
|
|
14998
|
+
this.service = service;
|
|
14999
|
+
this.logger = logger;
|
|
15000
|
+
}
|
|
15001
|
+
async call(input) {
|
|
15002
|
+
const params = callSchema.parse(input);
|
|
15003
|
+
const service = this.service();
|
|
15004
|
+
if (service === void 0) {
|
|
15005
|
+
throw new RpcError("FILE_VIEWER_UNAVAILABLE", "The Remote Host does not have DSH File Viewer available.");
|
|
15006
|
+
}
|
|
15007
|
+
const startedAt = performance.now();
|
|
15008
|
+
const signal = AbortSignal.timeout(3e4);
|
|
15009
|
+
let raw;
|
|
15010
|
+
try {
|
|
15011
|
+
raw = await service.handle(params.endpoint, params.payload, signal);
|
|
15012
|
+
} catch {
|
|
15013
|
+
this.logger?.warn("remote file viewer service failed", { endpoint: params.endpoint });
|
|
15014
|
+
throw new RpcError("FILE_VIEWER_ERROR", "The Remote File Viewer could not complete the request.");
|
|
15015
|
+
}
|
|
15016
|
+
const result = parseHostResult(raw);
|
|
15017
|
+
if (!result.ok) throw hostFailure(result);
|
|
15018
|
+
try {
|
|
15019
|
+
const value = params.endpoint === "stat" ? statResultSchema.parse(result.value) : params.endpoint === "readRange" ? rangeResultSchema.parse(result.value) : listResultSchema.parse(result.value);
|
|
15020
|
+
this.logger?.debug("remote file viewer call ok", {
|
|
15021
|
+
endpoint: params.endpoint,
|
|
15022
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
15023
|
+
});
|
|
15024
|
+
return value;
|
|
15025
|
+
} catch (error) {
|
|
15026
|
+
if (error instanceof external_exports.ZodError && params.endpoint === "list" && isOversizedListing(result.value)) {
|
|
15027
|
+
throw new RpcError(
|
|
15028
|
+
"RESPONSE_TOO_LARGE",
|
|
15029
|
+
`The remote directory contains more than ${MAX_REMOTE_DIRECTORY_ENTRIES} entries.`,
|
|
15030
|
+
{ maxEntries: MAX_REMOTE_DIRECTORY_ENTRIES },
|
|
15031
|
+
true
|
|
15032
|
+
);
|
|
15033
|
+
}
|
|
15034
|
+
throw error;
|
|
15035
|
+
}
|
|
15036
|
+
}
|
|
15037
|
+
};
|
|
15038
|
+
function parseHostResult(input) {
|
|
15039
|
+
if (typeof input !== "object" || input === null || !("ok" in input) || typeof input.ok !== "boolean") {
|
|
15040
|
+
throw new RpcError("FILE_VIEWER_INVALID_RESPONSE", "The Remote Host File Viewer returned an invalid response.");
|
|
15041
|
+
}
|
|
15042
|
+
return input;
|
|
15043
|
+
}
|
|
15044
|
+
function hostFailure(result) {
|
|
15045
|
+
const message = result.error?.message ?? "";
|
|
15046
|
+
if (/access denied/i.test(message)) {
|
|
15047
|
+
return new RpcError("ACCESS_DENIED", "The Remote File Viewer denied access to this locator.");
|
|
15048
|
+
}
|
|
15049
|
+
if (/does not exist|not exist|not found/i.test(message)) {
|
|
15050
|
+
return new RpcError("NOT_FOUND", "The requested remote file does not exist.");
|
|
15051
|
+
}
|
|
15052
|
+
if (result.error?.code === "cancelled") {
|
|
15053
|
+
return new RpcError("CANCELLED", "The Remote File Viewer request was cancelled.", void 0, true);
|
|
15054
|
+
}
|
|
15055
|
+
if (result.error?.code === "bad-request") {
|
|
15056
|
+
return new RpcError("INVALID_MESSAGE", "The Remote File Viewer request is invalid.");
|
|
15057
|
+
}
|
|
15058
|
+
return new RpcError("FILE_VIEWER_ERROR", "The Remote File Viewer could not complete the request.");
|
|
15059
|
+
}
|
|
15060
|
+
function isOversizedListing(value) {
|
|
15061
|
+
return typeof value === "object" && value !== null && "entries" in value && Array.isArray(value.entries) && value.entries.length > MAX_REMOTE_DIRECTORY_ENTRIES;
|
|
15062
|
+
}
|
|
15063
|
+
|
|
14543
15064
|
// src/server-connection.ts
|
|
14544
15065
|
var DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 8e3;
|
|
14545
15066
|
var HostServerConnection = class {
|
|
14546
|
-
constructor(config, identity, identities, api, connections, logger, createWebSocket = (url) => new WebSocket(url), rtcFactoryProvider) {
|
|
15067
|
+
constructor(config, identity, identities, api, connections, logger, createWebSocket = (url) => new WebSocket(url), rtcFactoryProvider, hostCapabilities = () => ["harness.api.v1"]) {
|
|
14547
15068
|
this.config = config;
|
|
14548
15069
|
this.identity = identity;
|
|
14549
15070
|
this.identities = identities;
|
|
@@ -14552,6 +15073,7 @@ var HostServerConnection = class {
|
|
|
14552
15073
|
this.logger = logger;
|
|
14553
15074
|
this.createWebSocket = createWebSocket;
|
|
14554
15075
|
this.rtcFactoryProvider = rtcFactoryProvider;
|
|
15076
|
+
this.hostCapabilities = hostCapabilities;
|
|
14555
15077
|
}
|
|
14556
15078
|
socket;
|
|
14557
15079
|
running;
|
|
@@ -14675,7 +15197,7 @@ var HostServerConnection = class {
|
|
|
14675
15197
|
accessToken: credentials.accessToken,
|
|
14676
15198
|
protocols: [PROTOCOL_VERSION],
|
|
14677
15199
|
clientVersion: PLUGIN_VERSION,
|
|
14678
|
-
capabilities: this.rtcFactoryProvider === void 0 || this.config.forceRelay ? ["transport.relay",
|
|
15200
|
+
capabilities: this.rtcFactoryProvider === void 0 || this.config.forceRelay ? ["transport.relay", ...this.hostCapabilities()] : ["transport.p2p", "transport.turn", "transport.relay", ...this.hostCapabilities()]
|
|
14679
15201
|
});
|
|
14680
15202
|
};
|
|
14681
15203
|
socket.onmessage = (event) => {
|
|
@@ -14701,7 +15223,7 @@ var HostServerConnection = class {
|
|
|
14701
15223
|
this.terminalError = code;
|
|
14702
15224
|
this.logger.error("server control frame failed", {
|
|
14703
15225
|
code,
|
|
14704
|
-
reason:
|
|
15226
|
+
reason: diagnosticReason4(error)
|
|
14705
15227
|
});
|
|
14706
15228
|
socket.close(4008, "invalid control frame");
|
|
14707
15229
|
});
|
|
@@ -14850,7 +15372,7 @@ var HostServerConnection = class {
|
|
|
14850
15372
|
if (!tunnel.noise.complete) throw new ControlConnectionError("SECURE_CHANNEL_FAILED", "Noise IK handshake did not complete.");
|
|
14851
15373
|
const viaWebRtc = tunnel.rtc !== void 0 && (tunnel.transport === "p2p" || tunnel.transport === "turn");
|
|
14852
15374
|
if (!viaWebRtc && tunnel.transport === "negotiating") tunnel.transport = "relay";
|
|
14853
|
-
const mode = viaWebRtc ? tunnel.transport === "turn" ? "TURN" : "P2P" : "Relay";
|
|
15375
|
+
const mode = viaWebRtc ? tunnel.transportMode ?? (tunnel.transport === "turn" ? "TURN" : "P2P") : "Relay";
|
|
14854
15376
|
const transmit = viaWebRtc ? (ciphertext) => tunnel.rtc.send(ciphertext) : (ciphertext) => this.sendRelay(tunnel, ciphertext);
|
|
14855
15377
|
const channel = new ServerNoiseChannel(tunnel, transmit, () => {
|
|
14856
15378
|
if (this.tunnels.get(tunnel.connectionId) === tunnel) this.tunnels.delete(tunnel.connectionId);
|
|
@@ -14960,19 +15482,20 @@ var HostServerConnection = class {
|
|
|
14960
15482
|
handleRtcOpened(tunnel, selected) {
|
|
14961
15483
|
if (this.tunnels.get(tunnel.connectionId) !== tunnel || tunnel.rtc === void 0) return;
|
|
14962
15484
|
tunnel.transport = selected;
|
|
15485
|
+
tunnel.transportMode = tunnel.rtc.selectedPathMode();
|
|
14963
15486
|
this.sendTransportSelected(tunnel, selected);
|
|
14964
15487
|
this.logger.info("webrtc data channel ready", {
|
|
14965
15488
|
connectionId: shortId3(tunnel.connectionId),
|
|
14966
15489
|
peerDeviceId: shortId3(tunnel.peer.deviceId),
|
|
14967
|
-
transport: selected
|
|
15490
|
+
transport: tunnel.transportMode ?? selected
|
|
14968
15491
|
});
|
|
14969
15492
|
}
|
|
14970
15493
|
async handleRtcFailed(tunnel, rtc, error) {
|
|
14971
15494
|
if (this.tunnels.get(tunnel.connectionId) !== tunnel || tunnel.rtc !== rtc) return;
|
|
14972
|
-
if (tunnel.
|
|
15495
|
+
if (tunnel.transport === "p2p" || tunnel.transport === "turn") {
|
|
14973
15496
|
this.logger.warn("webrtc data channel failed; disconnecting peer", {
|
|
14974
15497
|
connectionId: shortId3(tunnel.connectionId),
|
|
14975
|
-
reason:
|
|
15498
|
+
reason: diagnosticReason4(error)
|
|
14976
15499
|
});
|
|
14977
15500
|
await this.dropTunnel(tunnel.connectionId, "CONNECTION_FAILED");
|
|
14978
15501
|
return;
|
|
@@ -14982,7 +15505,7 @@ var HostServerConnection = class {
|
|
|
14982
15505
|
await rtc.close();
|
|
14983
15506
|
this.logger.warn("webrtc negotiation failed; falling back to relay", {
|
|
14984
15507
|
connectionId: shortId3(tunnel.connectionId),
|
|
14985
|
-
reason:
|
|
15508
|
+
reason: diagnosticReason4(error)
|
|
14986
15509
|
});
|
|
14987
15510
|
}
|
|
14988
15511
|
sendTransportSelected(tunnel, transport) {
|
|
@@ -15026,7 +15549,7 @@ var HostServerConnection = class {
|
|
|
15026
15549
|
} catch (error) {
|
|
15027
15550
|
this.logger.warn("remote connection RTC cleanup failed", {
|
|
15028
15551
|
connectionId: shortId3(connectionId),
|
|
15029
|
-
reason:
|
|
15552
|
+
reason: diagnosticReason4(error)
|
|
15030
15553
|
});
|
|
15031
15554
|
}
|
|
15032
15555
|
if (tunnel.channel !== void 0) {
|
|
@@ -15037,7 +15560,7 @@ var HostServerConnection = class {
|
|
|
15037
15560
|
await tunnel.channel.close(code).catch(() => void 0);
|
|
15038
15561
|
this.logger.warn("remote connection channel cleanup failed", {
|
|
15039
15562
|
connectionId: shortId3(connectionId),
|
|
15040
|
-
reason:
|
|
15563
|
+
reason: diagnosticReason4(error)
|
|
15041
15564
|
});
|
|
15042
15565
|
}
|
|
15043
15566
|
} else {
|
|
@@ -15205,7 +15728,7 @@ function shortId3(value) {
|
|
|
15205
15728
|
function asError2(error) {
|
|
15206
15729
|
return error instanceof Error ? error : new Error("Unknown Server connection error.");
|
|
15207
15730
|
}
|
|
15208
|
-
function
|
|
15731
|
+
function diagnosticReason4(error) {
|
|
15209
15732
|
const message = asError2(error).message.replace(/[\r\n]+/g, " ").slice(0, 160);
|
|
15210
15733
|
return message || "Unknown Server connection error.";
|
|
15211
15734
|
}
|
|
@@ -15269,7 +15792,7 @@ function crumbs(path) {
|
|
|
15269
15792
|
}
|
|
15270
15793
|
|
|
15271
15794
|
// src/harness-api-bridge.ts
|
|
15272
|
-
var
|
|
15795
|
+
var callSchema2 = external_exports.object({
|
|
15273
15796
|
method: external_exports.string().min(1).max(80),
|
|
15274
15797
|
rpcId: external_exports.string().min(1).max(128),
|
|
15275
15798
|
payload: external_exports.unknown()
|
|
@@ -15361,7 +15884,7 @@ var HarnessApiBridge = class {
|
|
|
15361
15884
|
host;
|
|
15362
15885
|
answer;
|
|
15363
15886
|
async call(input) {
|
|
15364
|
-
const params =
|
|
15887
|
+
const params = callSchema2.parse(input);
|
|
15365
15888
|
const method = this.methods.get(params.method);
|
|
15366
15889
|
if (method === void 0) throw deniedMethod(params.method);
|
|
15367
15890
|
const startedAt = performance.now();
|
|
@@ -15395,7 +15918,7 @@ var HarnessApiBridge = class {
|
|
|
15395
15918
|
method: params.method,
|
|
15396
15919
|
durationMs,
|
|
15397
15920
|
timedOut: signal.aborted,
|
|
15398
|
-
reason:
|
|
15921
|
+
reason: diagnosticReason5(error)
|
|
15399
15922
|
});
|
|
15400
15923
|
throw error;
|
|
15401
15924
|
}
|
|
@@ -15539,7 +16062,7 @@ function domainProperty(wireDomain) {
|
|
|
15539
16062
|
function deniedMethod(method) {
|
|
15540
16063
|
return new RpcError("METHOD_NOT_ALLOWED", `Harness API method ${JSON.stringify(method)} is not available in remote mode.`);
|
|
15541
16064
|
}
|
|
15542
|
-
function
|
|
16065
|
+
function diagnosticReason5(error) {
|
|
15543
16066
|
const message = error instanceof Error ? error.message : String(error);
|
|
15544
16067
|
return message.replace(/[\r\n]+/g, " ").slice(0, 160) || "Unknown Harness API failure.";
|
|
15545
16068
|
}
|
|
@@ -15618,205 +16141,13 @@ function shortId4(value) {
|
|
|
15618
16141
|
return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
|
|
15619
16142
|
}
|
|
15620
16143
|
|
|
15621
|
-
// src/werift-rtc.ts
|
|
15622
|
-
import { networkInterfaces } from "node:os";
|
|
15623
|
-
var cachedFactory;
|
|
15624
|
-
async function loadWeriftFactory() {
|
|
15625
|
-
if (cachedFactory !== void 0) return cachedFactory;
|
|
15626
|
-
try {
|
|
15627
|
-
const werift = await import("werift");
|
|
15628
|
-
cachedFactory = buildWeriftFactory(werift);
|
|
15629
|
-
return cachedFactory;
|
|
15630
|
-
} catch {
|
|
15631
|
-
return void 0;
|
|
15632
|
-
}
|
|
15633
|
-
}
|
|
15634
|
-
function buildWeriftFactory(werift) {
|
|
15635
|
-
return {
|
|
15636
|
-
create(configuration) {
|
|
15637
|
-
const hostIpv4 = detectHostIpv4();
|
|
15638
|
-
const raw = new werift.RTCPeerConnection({
|
|
15639
|
-
iceServers: configuration.iceServers ?? [],
|
|
15640
|
-
iceUseIpv6: false,
|
|
15641
|
-
iceUseLinkLocalAddress: false,
|
|
15642
|
-
...hostIpv4 === void 0 ? {} : { iceInterfaceAddresses: { udp4: hostIpv4 } }
|
|
15643
|
-
});
|
|
15644
|
-
let onIceCandidate = null;
|
|
15645
|
-
let onDataChannel = null;
|
|
15646
|
-
raw.onicecandidate = (event) => {
|
|
15647
|
-
if (onIceCandidate === null) return;
|
|
15648
|
-
onIceCandidate({ candidate: event.candidate === void 0 ? null : event.candidate.toJSON() });
|
|
15649
|
-
};
|
|
15650
|
-
raw.ondatachannel = (event) => {
|
|
15651
|
-
if (onDataChannel === null) return;
|
|
15652
|
-
onDataChannel({ channel: adaptDataChannel2(event.channel) });
|
|
15653
|
-
};
|
|
15654
|
-
const pc = {
|
|
15655
|
-
get connectionState() {
|
|
15656
|
-
return raw.connectionState;
|
|
15657
|
-
},
|
|
15658
|
-
get iceConnectionState() {
|
|
15659
|
-
return raw.iceConnectionState;
|
|
15660
|
-
},
|
|
15661
|
-
get iceGatheringState() {
|
|
15662
|
-
return raw.iceGatheringState;
|
|
15663
|
-
},
|
|
15664
|
-
get signalingState() {
|
|
15665
|
-
return raw.signalingState;
|
|
15666
|
-
},
|
|
15667
|
-
set onconnectionstatechange(value) {
|
|
15668
|
-
raw.onconnectionstatechange = value;
|
|
15669
|
-
},
|
|
15670
|
-
get onconnectionstatechange() {
|
|
15671
|
-
return raw.onconnectionstatechange;
|
|
15672
|
-
},
|
|
15673
|
-
set oniceconnectionstatechange(value) {
|
|
15674
|
-
raw.oniceconnectionstatechange = value;
|
|
15675
|
-
},
|
|
15676
|
-
get oniceconnectionstatechange() {
|
|
15677
|
-
return raw.oniceconnectionstatechange;
|
|
15678
|
-
},
|
|
15679
|
-
set onicegatheringstatechange(value) {
|
|
15680
|
-
raw.onicegatheringstatechange = value;
|
|
15681
|
-
},
|
|
15682
|
-
get onicegatheringstatechange() {
|
|
15683
|
-
return raw.onicegatheringstatechange;
|
|
15684
|
-
},
|
|
15685
|
-
set onicecandidate(value) {
|
|
15686
|
-
onIceCandidate = value;
|
|
15687
|
-
},
|
|
15688
|
-
get onicecandidate() {
|
|
15689
|
-
return onIceCandidate;
|
|
15690
|
-
},
|
|
15691
|
-
set ondatachannel(value) {
|
|
15692
|
-
onDataChannel = value;
|
|
15693
|
-
},
|
|
15694
|
-
get ondatachannel() {
|
|
15695
|
-
return onDataChannel;
|
|
15696
|
-
},
|
|
15697
|
-
createDataChannel(label, options) {
|
|
15698
|
-
return adaptDataChannel2(raw.createDataChannel(label, { ordered: options?.ordered ?? true }));
|
|
15699
|
-
},
|
|
15700
|
-
createOffer() {
|
|
15701
|
-
return raw.createOffer();
|
|
15702
|
-
},
|
|
15703
|
-
createAnswer() {
|
|
15704
|
-
return raw.createAnswer();
|
|
15705
|
-
},
|
|
15706
|
-
setLocalDescription(description) {
|
|
15707
|
-
return raw.setLocalDescription(description).then(() => void 0);
|
|
15708
|
-
},
|
|
15709
|
-
setRemoteDescription(description) {
|
|
15710
|
-
return raw.setRemoteDescription(description);
|
|
15711
|
-
},
|
|
15712
|
-
addIceCandidate(candidate) {
|
|
15713
|
-
return raw.addIceCandidate(candidate);
|
|
15714
|
-
},
|
|
15715
|
-
getStats() {
|
|
15716
|
-
return raw.getStats();
|
|
15717
|
-
},
|
|
15718
|
-
close() {
|
|
15719
|
-
void raw.close().catch(() => void 0);
|
|
15720
|
-
}
|
|
15721
|
-
};
|
|
15722
|
-
return pc;
|
|
15723
|
-
}
|
|
15724
|
-
};
|
|
15725
|
-
}
|
|
15726
|
-
function adaptDataChannel2(raw) {
|
|
15727
|
-
return {
|
|
15728
|
-
get label() {
|
|
15729
|
-
return raw.label;
|
|
15730
|
-
},
|
|
15731
|
-
get ordered() {
|
|
15732
|
-
return raw.ordered;
|
|
15733
|
-
},
|
|
15734
|
-
get readyState() {
|
|
15735
|
-
return raw.readyState;
|
|
15736
|
-
},
|
|
15737
|
-
get bufferedAmount() {
|
|
15738
|
-
return raw.bufferedAmount;
|
|
15739
|
-
},
|
|
15740
|
-
binaryType: "arraybuffer",
|
|
15741
|
-
set onopen(value) {
|
|
15742
|
-
raw.onopen = value;
|
|
15743
|
-
},
|
|
15744
|
-
get onopen() {
|
|
15745
|
-
return raw.onopen;
|
|
15746
|
-
},
|
|
15747
|
-
set onmessage(value) {
|
|
15748
|
-
raw.onmessage = value === null ? null : (event) => value({ data: toArrayBuffer2(event.data) });
|
|
15749
|
-
},
|
|
15750
|
-
get onmessage() {
|
|
15751
|
-
return null;
|
|
15752
|
-
},
|
|
15753
|
-
set onclose(value) {
|
|
15754
|
-
raw.onclose = value;
|
|
15755
|
-
},
|
|
15756
|
-
get onclose() {
|
|
15757
|
-
return raw.onclose;
|
|
15758
|
-
},
|
|
15759
|
-
set onerror(value) {
|
|
15760
|
-
raw.onerror = value;
|
|
15761
|
-
},
|
|
15762
|
-
get onerror() {
|
|
15763
|
-
return raw.onerror;
|
|
15764
|
-
},
|
|
15765
|
-
onbufferedamountlow: null,
|
|
15766
|
-
send(data) {
|
|
15767
|
-
const bytes = typeof data === "string" ? Buffer.byteLength(data) : data.byteLength;
|
|
15768
|
-
try {
|
|
15769
|
-
raw.send(typeof data === "string" ? data : Buffer.from(data));
|
|
15770
|
-
} catch (error) {
|
|
15771
|
-
console.error("[werift-send-error] bytes=" + bytes, error instanceof Error ? error.message : error);
|
|
15772
|
-
throw error;
|
|
15773
|
-
}
|
|
15774
|
-
},
|
|
15775
|
-
close() {
|
|
15776
|
-
raw.close();
|
|
15777
|
-
}
|
|
15778
|
-
};
|
|
15779
|
-
}
|
|
15780
|
-
function toArrayBuffer2(data) {
|
|
15781
|
-
if (typeof data === "string") return data;
|
|
15782
|
-
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
15783
|
-
}
|
|
15784
|
-
function detectHostIpv4() {
|
|
15785
|
-
const preferred = [];
|
|
15786
|
-
const fallback = [];
|
|
15787
|
-
for (const [name2, addresses] of Object.entries(networkInterfaces())) {
|
|
15788
|
-
if (isVirtualInterface(name2)) continue;
|
|
15789
|
-
for (const address of addresses ?? []) {
|
|
15790
|
-
if (address.internal || address.family !== "IPv4") continue;
|
|
15791
|
-
const ip = address.address;
|
|
15792
|
-
if (ip.startsWith("127.") || ip.startsWith("169.254.") || isCgnat(ip)) continue;
|
|
15793
|
-
if (isPrivate(ip)) preferred.push(ip);
|
|
15794
|
-
else fallback.push(ip);
|
|
15795
|
-
}
|
|
15796
|
-
}
|
|
15797
|
-
return preferred[0] ?? fallback[0];
|
|
15798
|
-
}
|
|
15799
|
-
function isVirtualInterface(name2) {
|
|
15800
|
-
return /^(utun|ppp|bridge|awdl|llw|gif|stf|anpi|ap\d|en[1-9]\d*$)/i.test(name2);
|
|
15801
|
-
}
|
|
15802
|
-
function isCgnat(ip) {
|
|
15803
|
-
const parts = ip.split(".").map(Number);
|
|
15804
|
-
return parts.length === 4 && parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127;
|
|
15805
|
-
}
|
|
15806
|
-
function isPrivate(ip) {
|
|
15807
|
-
const parts = ip.split(".").map(Number);
|
|
15808
|
-
if (parts.length !== 4) return false;
|
|
15809
|
-
if (parts[0] === 10) return true;
|
|
15810
|
-
if (parts[0] === 192 && parts[1] === 168) return true;
|
|
15811
|
-
return parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31;
|
|
15812
|
-
}
|
|
15813
|
-
|
|
15814
16144
|
// src/service.ts
|
|
15815
16145
|
var HostPluginRuntime = class {
|
|
15816
|
-
constructor(config, identities, apiProxy, logger, typertGateway) {
|
|
16146
|
+
constructor(config, identities, apiProxy, logger, typertGateway, fileViewerHost) {
|
|
15817
16147
|
this.config = config;
|
|
15818
16148
|
this.identities = identities;
|
|
15819
16149
|
this.logger = logger;
|
|
16150
|
+
this.fileViewerHost = fileViewerHost;
|
|
15820
16151
|
this.connections = new ConnectionController(this.identities, (_context, send) => {
|
|
15821
16152
|
const harnessApi = new HarnessApiBridge(
|
|
15822
16153
|
apiProxy,
|
|
@@ -15825,7 +16156,11 @@ var HostPluginRuntime = class {
|
|
|
15825
16156
|
this.logger,
|
|
15826
16157
|
typertGateway?.()
|
|
15827
16158
|
);
|
|
15828
|
-
|
|
16159
|
+
const fileViewer = new RemoteFileViewerBridge(
|
|
16160
|
+
() => this.fileViewerHost?.(),
|
|
16161
|
+
this.logger
|
|
16162
|
+
);
|
|
16163
|
+
return new RpcRouter(harnessApi, void 0, this.logger, fileViewer);
|
|
15829
16164
|
}, this.logger);
|
|
15830
16165
|
if (config.serverUrl !== void 0) {
|
|
15831
16166
|
this.serverApi = new HostServerApi(config.serverUrl, new ServerCredentialStore(identities.directory));
|
|
@@ -15962,7 +16297,8 @@ var HostPluginRuntime = class {
|
|
|
15962
16297
|
this.connections,
|
|
15963
16298
|
this.logger,
|
|
15964
16299
|
void 0,
|
|
15965
|
-
this.config.forceRelay ? void 0 : loadWeriftFactory
|
|
16300
|
+
this.config.forceRelay ? void 0 : loadWeriftFactory,
|
|
16301
|
+
() => this.fileViewerHost?.() === void 0 ? ["harness.api.v1"] : ["harness.api.v1", "fileviewer.read.v1"]
|
|
15966
16302
|
);
|
|
15967
16303
|
}
|
|
15968
16304
|
};
|
|
@@ -15970,6 +16306,67 @@ function shortId5(value) {
|
|
|
15970
16306
|
return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
|
|
15971
16307
|
}
|
|
15972
16308
|
|
|
16309
|
+
// src/remote-file-content-provider.ts
|
|
16310
|
+
function createRemoteFileContentProvider(call) {
|
|
16311
|
+
return {
|
|
16312
|
+
id: "dsh-remote-files",
|
|
16313
|
+
priority: 1e4,
|
|
16314
|
+
supports: () => true,
|
|
16315
|
+
async stat(locator, signal) {
|
|
16316
|
+
const value = await call("fileviewer.stat", { path: locator }, signal);
|
|
16317
|
+
if (!value.exists) return void 0;
|
|
16318
|
+
return {
|
|
16319
|
+
name: value.name,
|
|
16320
|
+
size: value.isDirectory ? 0 : value.size,
|
|
16321
|
+
mime: value.mime,
|
|
16322
|
+
mtimeMs: value.mtimeMs,
|
|
16323
|
+
isDirectory: value.isDirectory
|
|
16324
|
+
};
|
|
16325
|
+
},
|
|
16326
|
+
async read(locator, request) {
|
|
16327
|
+
if (!Number.isInteger(request.offset) || request.offset < 0) throw new Error("A non-negative integer offset is required.");
|
|
16328
|
+
if (!Number.isInteger(request.length) || request.length <= 0) throw new Error("A positive integer length is required.");
|
|
16329
|
+
const chunks = [];
|
|
16330
|
+
let received = 0;
|
|
16331
|
+
while (received < request.length) {
|
|
16332
|
+
request.signal.throwIfAborted();
|
|
16333
|
+
const length = Math.min(REMOTE_FILE_CHUNK_BYTES, request.length - received);
|
|
16334
|
+
const offset = request.offset + received;
|
|
16335
|
+
const range = await call("fileviewer.readRange", { path: locator, offset, length }, request.signal);
|
|
16336
|
+
if (range.offset !== offset) throw new Error("The Remote Host returned a mismatched file range.");
|
|
16337
|
+
const bytes = decodeBase64(range.data);
|
|
16338
|
+
if (bytes.byteLength > length) throw new Error("The Remote Host returned more file bytes than requested.");
|
|
16339
|
+
chunks.push(bytes);
|
|
16340
|
+
received += bytes.byteLength;
|
|
16341
|
+
if (range.eof || bytes.byteLength === 0) break;
|
|
16342
|
+
}
|
|
16343
|
+
const merged = new Uint8Array(received);
|
|
16344
|
+
let cursor = 0;
|
|
16345
|
+
for (const chunk of chunks) {
|
|
16346
|
+
merged.set(chunk, cursor);
|
|
16347
|
+
cursor += chunk.byteLength;
|
|
16348
|
+
}
|
|
16349
|
+
return merged;
|
|
16350
|
+
},
|
|
16351
|
+
async list(locator, signal) {
|
|
16352
|
+
const value = await call("fileviewer.list", { path: locator }, signal);
|
|
16353
|
+
return value.entries.map((entry) => ({
|
|
16354
|
+
locator: entry.path,
|
|
16355
|
+
name: entry.name,
|
|
16356
|
+
size: entry.isDirectory ? 0 : entry.size ?? 0,
|
|
16357
|
+
mtimeMs: entry.mtimeMs,
|
|
16358
|
+
isDirectory: entry.isDirectory
|
|
16359
|
+
}));
|
|
16360
|
+
}
|
|
16361
|
+
};
|
|
16362
|
+
}
|
|
16363
|
+
function decodeBase64(value) {
|
|
16364
|
+
const binary = atob(value);
|
|
16365
|
+
const bytes = new Uint8Array(binary.length);
|
|
16366
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
16367
|
+
return bytes;
|
|
16368
|
+
}
|
|
16369
|
+
|
|
15973
16370
|
// src/index.ts
|
|
15974
16371
|
var name = "dsh-remote";
|
|
15975
16372
|
function apply(ctx, input = {}) {
|
|
@@ -16012,7 +16409,14 @@ async function activate(ctx, input) {
|
|
|
16012
16409
|
const connection = ctx.get("connection");
|
|
16013
16410
|
const nativeTypertGateway = ctx.get("typertGateway");
|
|
16014
16411
|
const localTypertGateway = new TypertGatewaySwitch(nativeTypertGateway).local();
|
|
16015
|
-
const runtime = new HostPluginRuntime(
|
|
16412
|
+
const runtime = new HostPluginRuntime(
|
|
16413
|
+
config,
|
|
16414
|
+
hostIdentities,
|
|
16415
|
+
apiProxy,
|
|
16416
|
+
logger,
|
|
16417
|
+
() => localTypertGateway,
|
|
16418
|
+
() => ctx.get("fileViewerHost")
|
|
16419
|
+
);
|
|
16016
16420
|
let clientRuntime;
|
|
16017
16421
|
const hostControl = runtime;
|
|
16018
16422
|
if (config.serverUrl !== void 0 && apiProxy !== void 0 && connection !== void 0) {
|
|
@@ -16076,6 +16480,7 @@ export {
|
|
|
16076
16480
|
IdentityInvalidError,
|
|
16077
16481
|
IdentityStore,
|
|
16078
16482
|
PluginControlRuntime,
|
|
16483
|
+
RemoteFileViewerBridge,
|
|
16079
16484
|
RemoteHarnessApiProxy,
|
|
16080
16485
|
RpcError,
|
|
16081
16486
|
RpcRouter,
|
|
@@ -16084,6 +16489,7 @@ export {
|
|
|
16084
16489
|
ServerCredentialsInvalidError,
|
|
16085
16490
|
TypertGatewaySwitch,
|
|
16086
16491
|
apply,
|
|
16492
|
+
createRemoteFileContentProvider,
|
|
16087
16493
|
fingerprint,
|
|
16088
16494
|
name,
|
|
16089
16495
|
resolveConfig,
|