@dev-anywhere/relay 0.4.80 → 0.4.82
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/dist/{chunk-L2KEGMDB.js → chunk-5B2OTBDL.js} +183 -31
- package/dist/chunk-5B2OTBDL.js.map +1 -0
- package/dist/handlers/client.d.ts.map +1 -1
- package/dist/handlers/proxy.d.ts.map +1 -1
- package/dist/heartbeat.d.ts +9 -2
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/remote-file-bridge.d.ts +17 -4
- package/dist/remote-file-bridge.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-L2KEGMDB.js.map +0 -1
|
@@ -617,6 +617,21 @@ var relayControlDefinitions = [
|
|
|
617
617
|
url: z8.string().optional(),
|
|
618
618
|
expiresAt: z8.number().int().nonnegative().optional()
|
|
619
619
|
}),
|
|
620
|
+
control("remote_file_metadata_request", {
|
|
621
|
+
...RequiredRequestIdShape,
|
|
622
|
+
sessionId: IdSchema,
|
|
623
|
+
path: z8.string().min(1)
|
|
624
|
+
}),
|
|
625
|
+
control("remote_file_metadata_response", {
|
|
626
|
+
...RequiredRequestIdShape,
|
|
627
|
+
...RequestErrorShape,
|
|
628
|
+
sessionId: IdSchema,
|
|
629
|
+
path: z8.string().optional(),
|
|
630
|
+
success: z8.boolean(),
|
|
631
|
+
mimeType: z8.string().optional(),
|
|
632
|
+
size: z8.number().int().nonnegative().optional(),
|
|
633
|
+
fileName: z8.string().optional()
|
|
634
|
+
}),
|
|
620
635
|
control("remote_file_stream_request", {
|
|
621
636
|
streamId: IdSchema,
|
|
622
637
|
sessionId: IdSchema,
|
|
@@ -1768,6 +1783,10 @@ function handleProxyConnection(ws, registry, logger, chaos, remoteFileBridge) {
|
|
|
1768
1783
|
remoteFileBridge?.handleProxyControl(proxyWs.proxyId, result.message);
|
|
1769
1784
|
return;
|
|
1770
1785
|
}
|
|
1786
|
+
if (proxyWs.proxyId && result.message.type === "remote_file_metadata_response") {
|
|
1787
|
+
remoteFileBridge?.handleProxyControl(proxyWs.proxyId, result.message);
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1771
1790
|
if (proxyWs.proxyId && result.message.type === "remote_file_stream_complete") {
|
|
1772
1791
|
remoteFileBridge?.handleProxyControl(proxyWs.proxyId, result.message);
|
|
1773
1792
|
return;
|
|
@@ -2773,24 +2792,44 @@ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, v
|
|
|
2773
2792
|
);
|
|
2774
2793
|
return;
|
|
2775
2794
|
}
|
|
2776
|
-
|
|
2795
|
+
void remoteFileBridge.createUrl({
|
|
2777
2796
|
clientId: clientWs.clientId,
|
|
2778
2797
|
proxyId: targetProxyId,
|
|
2779
2798
|
sessionId: msg.sessionId,
|
|
2780
2799
|
path: msg.path,
|
|
2781
2800
|
disposition: msg.disposition
|
|
2801
|
+
}).then((result2) => {
|
|
2802
|
+
if (clientWs.readyState !== WebSocket7.OPEN) return;
|
|
2803
|
+
if (!result2.success) {
|
|
2804
|
+
sendRemoteFileUrlFailure(
|
|
2805
|
+
clientWs,
|
|
2806
|
+
msg.requestId,
|
|
2807
|
+
msg.sessionId,
|
|
2808
|
+
result2.error,
|
|
2809
|
+
result2.errorCode
|
|
2810
|
+
);
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
clientWs.send(
|
|
2814
|
+
serializeControl({
|
|
2815
|
+
type: "remote_file_url_response",
|
|
2816
|
+
requestId: msg.requestId,
|
|
2817
|
+
sessionId: msg.sessionId,
|
|
2818
|
+
path: result2.path,
|
|
2819
|
+
success: true,
|
|
2820
|
+
url: result2.url,
|
|
2821
|
+
expiresAt: result2.expiresAt
|
|
2822
|
+
})
|
|
2823
|
+
);
|
|
2824
|
+
}).catch((err) => {
|
|
2825
|
+
if (clientWs.readyState !== WebSocket7.OPEN) return;
|
|
2826
|
+
sendRemoteFileUrlFailure(
|
|
2827
|
+
clientWs,
|
|
2828
|
+
msg.requestId,
|
|
2829
|
+
msg.sessionId,
|
|
2830
|
+
err instanceof Error ? err.message : String(err)
|
|
2831
|
+
);
|
|
2782
2832
|
});
|
|
2783
|
-
clientWs.send(
|
|
2784
|
-
serializeControl({
|
|
2785
|
-
type: "remote_file_url_response",
|
|
2786
|
-
requestId: msg.requestId,
|
|
2787
|
-
sessionId: msg.sessionId,
|
|
2788
|
-
path: msg.path,
|
|
2789
|
-
success: true,
|
|
2790
|
-
url,
|
|
2791
|
-
expiresAt
|
|
2792
|
-
})
|
|
2793
|
-
);
|
|
2794
2833
|
return;
|
|
2795
2834
|
}
|
|
2796
2835
|
if (msg.type === "remote_file_upload_url_request") {
|
|
@@ -2993,12 +3032,20 @@ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, v
|
|
|
2993
3032
|
closeRejectedClientProtocol(clientWs);
|
|
2994
3033
|
}
|
|
2995
3034
|
});
|
|
2996
|
-
clientWs.on("close", () => {
|
|
3035
|
+
clientWs.on("close", (code, reason) => {
|
|
2997
3036
|
registry.removeClientWs(clientWs);
|
|
2998
3037
|
if (clientWs.clientId) {
|
|
2999
3038
|
registry.unbindClientById(clientWs.clientId);
|
|
3000
3039
|
}
|
|
3001
|
-
logger.info(
|
|
3040
|
+
logger.info(
|
|
3041
|
+
{
|
|
3042
|
+
clientId: clientWs.clientId,
|
|
3043
|
+
boundProxyId: clientWs.boundProxyId,
|
|
3044
|
+
code,
|
|
3045
|
+
reason: reason.toString() || void 0
|
|
3046
|
+
},
|
|
3047
|
+
"Client disconnected"
|
|
3048
|
+
);
|
|
3002
3049
|
});
|
|
3003
3050
|
clientWs.on("error", (err) => {
|
|
3004
3051
|
logger.error({ err }, "Client WebSocket error");
|
|
@@ -3009,7 +3056,7 @@ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, v
|
|
|
3009
3056
|
function markAlive(ws) {
|
|
3010
3057
|
ws.isAlive = true;
|
|
3011
3058
|
}
|
|
3012
|
-
function setupHeartbeat(wss, interval = 3e4) {
|
|
3059
|
+
function setupHeartbeat(wss, interval = 3e4, options = {}) {
|
|
3013
3060
|
wss.on("connection", (ws) => {
|
|
3014
3061
|
markAlive(ws);
|
|
3015
3062
|
ws.on("pong", () => {
|
|
@@ -3020,6 +3067,13 @@ function setupHeartbeat(wss, interval = 3e4) {
|
|
|
3020
3067
|
for (const ws of wss.clients) {
|
|
3021
3068
|
const sock = ws;
|
|
3022
3069
|
if (sock.isAlive === false) {
|
|
3070
|
+
options.logger?.warn(
|
|
3071
|
+
{
|
|
3072
|
+
peerType: options.peerType,
|
|
3073
|
+
...options.describePeer?.(ws) ?? {}
|
|
3074
|
+
},
|
|
3075
|
+
"WebSocket heartbeat timeout"
|
|
3076
|
+
);
|
|
3023
3077
|
sock.terminate();
|
|
3024
3078
|
continue;
|
|
3025
3079
|
}
|
|
@@ -3578,8 +3632,10 @@ function encodeContentDisposition(disposition, fileName) {
|
|
|
3578
3632
|
function statusForErrorCode(errorCode) {
|
|
3579
3633
|
switch (errorCode) {
|
|
3580
3634
|
case ControlErrorCode.SESSION_NOT_FOUND:
|
|
3635
|
+
case ControlErrorCode.PATH_NOT_FOUND:
|
|
3581
3636
|
return 404;
|
|
3582
3637
|
case ControlErrorCode.INVALID_PATH:
|
|
3638
|
+
case ControlErrorCode.PATH_NOT_DIRECTORY:
|
|
3583
3639
|
return 400;
|
|
3584
3640
|
case ControlErrorCode.PATH_ACCESS_DENIED:
|
|
3585
3641
|
return 403;
|
|
@@ -3594,22 +3650,54 @@ var RemoteFileBridge = class {
|
|
|
3594
3650
|
deps;
|
|
3595
3651
|
tokens = /* @__PURE__ */ new Map();
|
|
3596
3652
|
uploadTokens = /* @__PURE__ */ new Map();
|
|
3653
|
+
pendingUrlMetadata = /* @__PURE__ */ new Map();
|
|
3597
3654
|
pendingStreams = /* @__PURE__ */ new Map();
|
|
3598
3655
|
pendingUploads = /* @__PURE__ */ new Map();
|
|
3599
3656
|
createUrl(input) {
|
|
3600
3657
|
this.cleanupExpiredTokens();
|
|
3601
|
-
const
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3658
|
+
const proxyWs = this.deps.registry.getProxy(input.proxyId);
|
|
3659
|
+
if (!proxyWs || proxyWs.readyState !== WebSocket11.OPEN) {
|
|
3660
|
+
return Promise.resolve({
|
|
3661
|
+
success: false,
|
|
3662
|
+
path: input.path,
|
|
3663
|
+
error: "\u5F53\u524D\u672A\u8FDE\u63A5\u5F00\u53D1\u673A",
|
|
3664
|
+
errorCode: ControlErrorCode.PROXY_OFFLINE
|
|
3665
|
+
});
|
|
3666
|
+
}
|
|
3667
|
+
const requestId = nanoid3(21);
|
|
3668
|
+
return new Promise((resolve2) => {
|
|
3669
|
+
const timer = setTimeout(() => {
|
|
3670
|
+
this.pendingUrlMetadata.delete(requestId);
|
|
3671
|
+
resolve2({
|
|
3672
|
+
success: false,
|
|
3673
|
+
path: input.path,
|
|
3674
|
+
error: "\u8BFB\u53D6\u6587\u4EF6\u5143\u6570\u636E\u8D85\u65F6",
|
|
3675
|
+
errorCode: ControlErrorCode.UNKNOWN
|
|
3676
|
+
});
|
|
3677
|
+
}, REMOTE_FILE_METADATA_TIMEOUT_MS);
|
|
3678
|
+
this.pendingUrlMetadata.set(requestId, {
|
|
3679
|
+
requestId,
|
|
3680
|
+
clientId: input.clientId,
|
|
3681
|
+
proxyId: input.proxyId,
|
|
3682
|
+
sessionId: input.sessionId,
|
|
3683
|
+
path: input.path,
|
|
3684
|
+
disposition: input.disposition,
|
|
3685
|
+
timer,
|
|
3686
|
+
resolve: resolve2
|
|
3687
|
+
});
|
|
3688
|
+
proxyWs.send(
|
|
3689
|
+
serializeControl({
|
|
3690
|
+
type: "remote_file_metadata_request",
|
|
3691
|
+
requestId,
|
|
3692
|
+
sessionId: input.sessionId,
|
|
3693
|
+
path: input.path
|
|
3694
|
+
})
|
|
3695
|
+
);
|
|
3696
|
+
this.deps.logger.info(
|
|
3697
|
+
{ requestId, proxyId: input.proxyId, sessionId: input.sessionId, path: input.path },
|
|
3698
|
+
"Remote file URL metadata requested"
|
|
3699
|
+
);
|
|
3611
3700
|
});
|
|
3612
|
-
return { url: `/api/remote-files/${token}`, expiresAt };
|
|
3613
3701
|
}
|
|
3614
3702
|
createUploadUrl(input) {
|
|
3615
3703
|
this.cleanupExpiredTokens();
|
|
@@ -3748,6 +3836,9 @@ var RemoteFileBridge = class {
|
|
|
3748
3836
|
if (msg.type === "remote_file_upload_stream_response") {
|
|
3749
3837
|
return this.handleUploadResponse(proxyId, msg);
|
|
3750
3838
|
}
|
|
3839
|
+
if (msg.type === "remote_file_metadata_response") {
|
|
3840
|
+
return this.handleUrlMetadataResponse(proxyId, msg);
|
|
3841
|
+
}
|
|
3751
3842
|
const pending = this.pendingStreams.get(msg.streamId);
|
|
3752
3843
|
if (!pending) return true;
|
|
3753
3844
|
if (pending.token.proxyId !== proxyId) {
|
|
@@ -3764,6 +3855,50 @@ var RemoteFileBridge = class {
|
|
|
3764
3855
|
this.handleStreamComplete(pending, msg);
|
|
3765
3856
|
return true;
|
|
3766
3857
|
}
|
|
3858
|
+
issueUrlToken(input) {
|
|
3859
|
+
const token = nanoid3(32);
|
|
3860
|
+
const expiresAt = Date.now() + REMOTE_FILE_TOKEN_TTL_MS;
|
|
3861
|
+
this.tokens.set(token, {
|
|
3862
|
+
token,
|
|
3863
|
+
clientId: input.clientId,
|
|
3864
|
+
proxyId: input.proxyId,
|
|
3865
|
+
sessionId: input.sessionId,
|
|
3866
|
+
path: input.path,
|
|
3867
|
+
disposition: input.disposition,
|
|
3868
|
+
expiresAt
|
|
3869
|
+
});
|
|
3870
|
+
return { url: `/api/remote-files/${token}`, expiresAt };
|
|
3871
|
+
}
|
|
3872
|
+
handleUrlMetadataResponse(proxyId, msg) {
|
|
3873
|
+
const pending = this.pendingUrlMetadata.get(msg.requestId);
|
|
3874
|
+
if (!pending) return true;
|
|
3875
|
+
if (pending.proxyId !== proxyId) {
|
|
3876
|
+
this.deps.logger.warn(
|
|
3877
|
+
{ requestId: msg.requestId, expectedProxyId: pending.proxyId, proxyId },
|
|
3878
|
+
"Remote file metadata ignored: proxy mismatch"
|
|
3879
|
+
);
|
|
3880
|
+
return true;
|
|
3881
|
+
}
|
|
3882
|
+
this.pendingUrlMetadata.delete(msg.requestId);
|
|
3883
|
+
clearTimeout(pending.timer);
|
|
3884
|
+
if (!msg.success) {
|
|
3885
|
+
pending.resolve({
|
|
3886
|
+
success: false,
|
|
3887
|
+
path: pending.path,
|
|
3888
|
+
error: msg.error ?? "\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25",
|
|
3889
|
+
...msg.errorCode ? { errorCode: msg.errorCode } : {}
|
|
3890
|
+
});
|
|
3891
|
+
return true;
|
|
3892
|
+
}
|
|
3893
|
+
const { url, expiresAt } = this.issueUrlToken(pending);
|
|
3894
|
+
pending.resolve({
|
|
3895
|
+
success: true,
|
|
3896
|
+
path: pending.path,
|
|
3897
|
+
url,
|
|
3898
|
+
expiresAt
|
|
3899
|
+
});
|
|
3900
|
+
return true;
|
|
3901
|
+
}
|
|
3767
3902
|
handleProxyBinary(proxyId, data) {
|
|
3768
3903
|
const decoded = decodeFileStreamFrame(data);
|
|
3769
3904
|
if (!decoded) return false;
|
|
@@ -4091,10 +4226,27 @@ function createRelayServer(options) {
|
|
|
4091
4226
|
voiceTtsWss.on("connection", (ws) => {
|
|
4092
4227
|
handleVoiceTtsConnection(ws, voiceConfigStore, logger, voiceProviders);
|
|
4093
4228
|
});
|
|
4094
|
-
const proxyHeartbeat = setupHeartbeat(proxyWss, heartbeatInterval
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4229
|
+
const proxyHeartbeat = setupHeartbeat(proxyWss, heartbeatInterval, {
|
|
4230
|
+
logger,
|
|
4231
|
+
peerType: "proxy",
|
|
4232
|
+
describePeer: (ws) => ({ proxyId: ws.proxyId })
|
|
4233
|
+
});
|
|
4234
|
+
const clientHeartbeat = setupHeartbeat(clientWss, heartbeatInterval, {
|
|
4235
|
+
logger,
|
|
4236
|
+
peerType: "client",
|
|
4237
|
+
describePeer: (ws) => ({
|
|
4238
|
+
clientId: ws.clientId,
|
|
4239
|
+
boundProxyId: ws.boundProxyId
|
|
4240
|
+
})
|
|
4241
|
+
});
|
|
4242
|
+
const voiceAsrHeartbeat = setupHeartbeat(voiceAsrWss, heartbeatInterval, {
|
|
4243
|
+
logger,
|
|
4244
|
+
peerType: "voice-asr"
|
|
4245
|
+
});
|
|
4246
|
+
const voiceTtsHeartbeat = setupHeartbeat(voiceTtsWss, heartbeatInterval, {
|
|
4247
|
+
logger,
|
|
4248
|
+
peerType: "voice-tts"
|
|
4249
|
+
});
|
|
4098
4250
|
async function close() {
|
|
4099
4251
|
clearInterval(proxyHeartbeat);
|
|
4100
4252
|
clearInterval(clientHeartbeat);
|
|
@@ -4153,4 +4305,4 @@ export {
|
|
|
4153
4305
|
parseRelayChaosFromEnv,
|
|
4154
4306
|
createRelayServer
|
|
4155
4307
|
};
|
|
4156
|
-
//# sourceMappingURL=chunk-
|
|
4308
|
+
//# sourceMappingURL=chunk-5B2OTBDL.js.map
|