@dev-anywhere/relay 0.4.73 → 0.4.74
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-UV7DNQUI.js → chunk-XY4ATTV2.js} +628 -73
- package/dist/chunk-XY4ATTV2.js.map +1 -0
- package/dist/handlers/client.d.ts +2 -1
- package/dist/handlers/client.d.ts.map +1 -1
- package/dist/handlers/proxy.d.ts +2 -1
- package/dist/handlers/proxy.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/remote-file-bridge.d.ts +57 -0
- package/dist/remote-file-bridge.d.ts.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-UV7DNQUI.js.map +0 -1
|
@@ -562,7 +562,8 @@ var RequestErrorShape = {
|
|
|
562
562
|
error: z8.string().optional(),
|
|
563
563
|
errorCode: ControlErrorCodeSchema.optional()
|
|
564
564
|
};
|
|
565
|
-
var
|
|
565
|
+
var RemoteFileDispositionSchema = z8.enum(["inline", "download"]);
|
|
566
|
+
var RemoteFileUploadKindSchema = z8.enum(["clipboard_image", "file"]);
|
|
566
567
|
function control(type, shape, directions) {
|
|
567
568
|
return {
|
|
568
569
|
type,
|
|
@@ -601,6 +602,82 @@ var relayControlDefinitions = [
|
|
|
601
602
|
control("relay_client_kicked", {
|
|
602
603
|
reason: z8.string().optional()
|
|
603
604
|
}),
|
|
605
|
+
control("remote_file_url_request", {
|
|
606
|
+
...RequiredRequestIdShape,
|
|
607
|
+
sessionId: IdSchema,
|
|
608
|
+
path: z8.string().min(1),
|
|
609
|
+
disposition: RemoteFileDispositionSchema
|
|
610
|
+
}),
|
|
611
|
+
control("remote_file_url_response", {
|
|
612
|
+
...RequiredRequestIdShape,
|
|
613
|
+
...RequestErrorShape,
|
|
614
|
+
sessionId: IdSchema,
|
|
615
|
+
path: z8.string().optional(),
|
|
616
|
+
success: z8.boolean(),
|
|
617
|
+
url: z8.string().optional(),
|
|
618
|
+
expiresAt: z8.number().int().nonnegative().optional()
|
|
619
|
+
}),
|
|
620
|
+
control("remote_file_stream_request", {
|
|
621
|
+
streamId: IdSchema,
|
|
622
|
+
sessionId: IdSchema,
|
|
623
|
+
path: z8.string().min(1),
|
|
624
|
+
disposition: RemoteFileDispositionSchema
|
|
625
|
+
}),
|
|
626
|
+
control("remote_file_stream_response", {
|
|
627
|
+
...RequestErrorShape,
|
|
628
|
+
streamId: IdSchema,
|
|
629
|
+
sessionId: IdSchema,
|
|
630
|
+
success: z8.boolean(),
|
|
631
|
+
path: z8.string().optional(),
|
|
632
|
+
mimeType: z8.string().optional(),
|
|
633
|
+
size: z8.number().int().nonnegative().optional(),
|
|
634
|
+
fileName: z8.string().optional()
|
|
635
|
+
}),
|
|
636
|
+
control("remote_file_stream_complete", {
|
|
637
|
+
...RequestErrorShape,
|
|
638
|
+
streamId: IdSchema,
|
|
639
|
+
success: z8.boolean()
|
|
640
|
+
}),
|
|
641
|
+
control("remote_file_stream_cancel", {
|
|
642
|
+
streamId: IdSchema
|
|
643
|
+
}),
|
|
644
|
+
control("remote_file_upload_url_request", {
|
|
645
|
+
...RequiredRequestIdShape,
|
|
646
|
+
sessionId: IdSchema,
|
|
647
|
+
kind: RemoteFileUploadKindSchema,
|
|
648
|
+
fileName: z8.string().optional(),
|
|
649
|
+
mimeType: z8.string().min(1),
|
|
650
|
+
size: z8.number().int().nonnegative().optional()
|
|
651
|
+
}),
|
|
652
|
+
control("remote_file_upload_url_response", {
|
|
653
|
+
...RequiredRequestIdShape,
|
|
654
|
+
...RequestErrorShape,
|
|
655
|
+
sessionId: IdSchema,
|
|
656
|
+
success: z8.boolean(),
|
|
657
|
+
uploadUrl: z8.string().optional(),
|
|
658
|
+
expiresAt: z8.number().int().nonnegative().optional()
|
|
659
|
+
}),
|
|
660
|
+
control("remote_file_upload_stream_request", {
|
|
661
|
+
uploadId: IdSchema,
|
|
662
|
+
sessionId: IdSchema,
|
|
663
|
+
kind: RemoteFileUploadKindSchema,
|
|
664
|
+
fileName: z8.string().optional(),
|
|
665
|
+
mimeType: z8.string().min(1),
|
|
666
|
+
size: z8.number().int().nonnegative().optional()
|
|
667
|
+
}),
|
|
668
|
+
control("remote_file_upload_stream_complete", {
|
|
669
|
+
uploadId: IdSchema
|
|
670
|
+
}),
|
|
671
|
+
control("remote_file_upload_stream_cancel", {
|
|
672
|
+
uploadId: IdSchema
|
|
673
|
+
}),
|
|
674
|
+
control("remote_file_upload_stream_response", {
|
|
675
|
+
...RequestErrorShape,
|
|
676
|
+
uploadId: IdSchema,
|
|
677
|
+
sessionId: IdSchema,
|
|
678
|
+
success: z8.boolean(),
|
|
679
|
+
path: z8.string().optional()
|
|
680
|
+
}),
|
|
604
681
|
control("proxy_select", { ...RequestIdShape, proxyId: IdSchema }),
|
|
605
682
|
control("proxy_select_response", {
|
|
606
683
|
...RequestIdShape,
|
|
@@ -768,70 +845,6 @@ var relayControlDefinitions = [
|
|
|
768
845
|
}, "proxy_to_client"),
|
|
769
846
|
// 客户端发送到 PTY 的原始字节(ANSI 序列),不追加换行
|
|
770
847
|
control("remote_input_raw", { sessionId: IdSchema, data: z8.string(), traceId: IdSchema.optional() }, "client_to_proxy"),
|
|
771
|
-
control("clipboard_image_upload", {
|
|
772
|
-
...RequestIdShape,
|
|
773
|
-
sessionId: IdSchema,
|
|
774
|
-
mimeType: ClipboardImageMimeTypeSchema,
|
|
775
|
-
dataBase64: z8.string().min(1),
|
|
776
|
-
fileName: z8.string().optional()
|
|
777
|
-
}, "client_to_proxy"),
|
|
778
|
-
control("clipboard_image_upload_response", {
|
|
779
|
-
...RequestIdShape,
|
|
780
|
-
...RequestErrorShape,
|
|
781
|
-
sessionId: IdSchema,
|
|
782
|
-
success: z8.boolean(),
|
|
783
|
-
// success=false 时 proxy 没有有效 path 可填;保持 optional 以避免占位空字符串通过校验。
|
|
784
|
-
path: z8.string().optional()
|
|
785
|
-
}, "proxy_to_client"),
|
|
786
|
-
control("image_preview_request", {
|
|
787
|
-
...RequestIdShape,
|
|
788
|
-
sessionId: IdSchema,
|
|
789
|
-
path: z8.string().min(1)
|
|
790
|
-
}, "client_to_proxy"),
|
|
791
|
-
control("image_preview_response", {
|
|
792
|
-
...RequestIdShape,
|
|
793
|
-
...RequestErrorShape,
|
|
794
|
-
sessionId: IdSchema,
|
|
795
|
-
success: z8.boolean(),
|
|
796
|
-
// 同 clipboard_image_upload_response:失败时 proxy 不一定有路径。
|
|
797
|
-
path: z8.string().optional(),
|
|
798
|
-
mimeType: ClipboardImageMimeTypeSchema.optional(),
|
|
799
|
-
dataBase64: z8.string().optional(),
|
|
800
|
-
size: z8.number().int().nonnegative().optional()
|
|
801
|
-
}, "proxy_to_client"),
|
|
802
|
-
// 任意文件下载: 与 image_preview 形状对称, 只是 mimeType 不限定为图片;
|
|
803
|
-
// 单租户场景下 path 任意 (不受 previewRoots 限制), 由 proxy 端 size cap 兜底。
|
|
804
|
-
control("file_download_request", {
|
|
805
|
-
...RequestIdShape,
|
|
806
|
-
sessionId: IdSchema,
|
|
807
|
-
path: z8.string().min(1)
|
|
808
|
-
}, "client_to_proxy"),
|
|
809
|
-
control("file_download_response", {
|
|
810
|
-
...RequestIdShape,
|
|
811
|
-
...RequestErrorShape,
|
|
812
|
-
sessionId: IdSchema,
|
|
813
|
-
success: z8.boolean(),
|
|
814
|
-
path: z8.string().optional(),
|
|
815
|
-
mimeType: z8.string().optional(),
|
|
816
|
-
dataBase64: z8.string().optional(),
|
|
817
|
-
size: z8.number().int().nonnegative().optional()
|
|
818
|
-
}, "proxy_to_client"),
|
|
819
|
-
// 任意文件上传: 复用 clipboard_image_upload 的形状, mimeType 放开 + fileName 必填,
|
|
820
|
-
// 由 proxy 端写入 session cwd 的 .dev-anywhere/uploads/ 子目录, 返回相对路径供 web 拼成 @path。
|
|
821
|
-
control("file_upload_request", {
|
|
822
|
-
...RequestIdShape,
|
|
823
|
-
sessionId: IdSchema,
|
|
824
|
-
mimeType: z8.string().min(1),
|
|
825
|
-
dataBase64: z8.string().min(1),
|
|
826
|
-
fileName: z8.string().min(1)
|
|
827
|
-
}, "client_to_proxy"),
|
|
828
|
-
control("file_upload_response", {
|
|
829
|
-
...RequestIdShape,
|
|
830
|
-
...RequestErrorShape,
|
|
831
|
-
sessionId: IdSchema,
|
|
832
|
-
success: z8.boolean(),
|
|
833
|
-
path: z8.string().optional()
|
|
834
|
-
}, "proxy_to_client"),
|
|
835
848
|
// 客户端询问 proxy 的环境信息 (home 路径等), client -> proxy -> response
|
|
836
849
|
// FilePathPicker 用 homePath 作为 select 模式下的默认起点, 新建会话时打开即可浏览
|
|
837
850
|
control("proxy_info_request", RequestIdShape, "client_to_proxy"),
|
|
@@ -983,6 +996,42 @@ var RelayCloseCode = {
|
|
|
983
996
|
var SID_LEN_BYTES = 1;
|
|
984
997
|
var SEQ_BYTES = 4;
|
|
985
998
|
var HEADER_FIXED_BYTES = SID_LEN_BYTES + SEQ_BYTES;
|
|
999
|
+
var FILE_STREAM_MARKER = 0;
|
|
1000
|
+
var FILE_STREAM_ID_LEN_BYTES = 1;
|
|
1001
|
+
var FILE_STREAM_HEADER_FIXED_BYTES = 1 + FILE_STREAM_ID_LEN_BYTES + SEQ_BYTES;
|
|
1002
|
+
function encodeFileStreamFrame(streamId, chunkSeq, data) {
|
|
1003
|
+
const streamIdBytes = new TextEncoder().encode(streamId);
|
|
1004
|
+
if (streamIdBytes.length === 0 || streamIdBytes.length > 255) {
|
|
1005
|
+
throw new RangeError(`streamId byte length must be 1-255, got ${streamIdBytes.length} (streamId=${streamId})`);
|
|
1006
|
+
}
|
|
1007
|
+
if (!Number.isInteger(chunkSeq) || chunkSeq < 0 || chunkSeq > 4294967295) {
|
|
1008
|
+
throw new RangeError(`chunkSeq must be a uint32, got ${chunkSeq}`);
|
|
1009
|
+
}
|
|
1010
|
+
const frame = new Uint8Array(1 + FILE_STREAM_ID_LEN_BYTES + streamIdBytes.length + SEQ_BYTES + data.length);
|
|
1011
|
+
frame[0] = FILE_STREAM_MARKER;
|
|
1012
|
+
frame[1] = streamIdBytes.length;
|
|
1013
|
+
frame.set(streamIdBytes, 1 + FILE_STREAM_ID_LEN_BYTES);
|
|
1014
|
+
const seqOffset = 1 + FILE_STREAM_ID_LEN_BYTES + streamIdBytes.length;
|
|
1015
|
+
new DataView(frame.buffer, frame.byteOffset + seqOffset, SEQ_BYTES).setUint32(0, chunkSeq, true);
|
|
1016
|
+
frame.set(data, seqOffset + SEQ_BYTES);
|
|
1017
|
+
return frame;
|
|
1018
|
+
}
|
|
1019
|
+
function decodeFileStreamFrame(view) {
|
|
1020
|
+
if (view.length < FILE_STREAM_HEADER_FIXED_BYTES)
|
|
1021
|
+
return null;
|
|
1022
|
+
if (view[0] !== FILE_STREAM_MARKER)
|
|
1023
|
+
return null;
|
|
1024
|
+
const streamIdLen = view[1];
|
|
1025
|
+
if (streamIdLen === 0)
|
|
1026
|
+
return null;
|
|
1027
|
+
if (view.length < 1 + FILE_STREAM_ID_LEN_BYTES + streamIdLen + SEQ_BYTES)
|
|
1028
|
+
return null;
|
|
1029
|
+
const streamId = new TextDecoder().decode(view.subarray(1 + FILE_STREAM_ID_LEN_BYTES, 1 + FILE_STREAM_ID_LEN_BYTES + streamIdLen));
|
|
1030
|
+
const seqOffset = 1 + FILE_STREAM_ID_LEN_BYTES + streamIdLen;
|
|
1031
|
+
const chunkSeq = new DataView(view.buffer, view.byteOffset + seqOffset, SEQ_BYTES).getUint32(0, true);
|
|
1032
|
+
const data = view.subarray(seqOffset + SEQ_BYTES);
|
|
1033
|
+
return { streamId, chunkSeq, data };
|
|
1034
|
+
}
|
|
986
1035
|
|
|
987
1036
|
// ../../packages/shared/dist/state-machine.js
|
|
988
1037
|
function computeAbsorbingSet(transitions) {
|
|
@@ -1612,7 +1661,7 @@ function rejectNotRegistered(ws) {
|
|
|
1612
1661
|
})
|
|
1613
1662
|
);
|
|
1614
1663
|
}
|
|
1615
|
-
function handleProxyConnection(ws, registry, logger, chaos) {
|
|
1664
|
+
function handleProxyConnection(ws, registry, logger, chaos, remoteFileBridge) {
|
|
1616
1665
|
const proxyWs = ws;
|
|
1617
1666
|
proxyWs.isAlive = true;
|
|
1618
1667
|
proxyWs.on("pong", () => {
|
|
@@ -1624,6 +1673,9 @@ function handleProxyConnection(ws, registry, logger, chaos) {
|
|
|
1624
1673
|
logger.warn({ size: data.length }, "Binary frame rejected: invalid size");
|
|
1625
1674
|
return;
|
|
1626
1675
|
}
|
|
1676
|
+
if (proxyWs.proxyId && remoteFileBridge?.handleProxyBinary(proxyWs.proxyId, data)) {
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1627
1679
|
const sessionIdLen = data[0];
|
|
1628
1680
|
if (sessionIdLen === 0 || sessionIdLen > 255 || data.length < 1 + sessionIdLen) {
|
|
1629
1681
|
logger.warn(
|
|
@@ -1712,6 +1764,18 @@ function handleProxyConnection(ws, registry, logger, chaos) {
|
|
|
1712
1764
|
return;
|
|
1713
1765
|
}
|
|
1714
1766
|
if (result.kind === "control") {
|
|
1767
|
+
if (proxyWs.proxyId && result.message.type === "remote_file_stream_response") {
|
|
1768
|
+
remoteFileBridge?.handleProxyControl(proxyWs.proxyId, result.message);
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
if (proxyWs.proxyId && result.message.type === "remote_file_stream_complete") {
|
|
1772
|
+
remoteFileBridge?.handleProxyControl(proxyWs.proxyId, result.message);
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
if (proxyWs.proxyId && result.message.type === "remote_file_upload_stream_response") {
|
|
1776
|
+
remoteFileBridge?.handleProxyControl(proxyWs.proxyId, result.message);
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1715
1779
|
if (isProxyToClientRelayControlType(result.message.type)) {
|
|
1716
1780
|
if (!proxyWs.proxyId) {
|
|
1717
1781
|
rejectNotRegistered(proxyWs);
|
|
@@ -2610,7 +2674,31 @@ function sendRelayProxyProbeFailure(ws, requestId, error, chaos) {
|
|
|
2610
2674
|
}
|
|
2611
2675
|
ws.send(response);
|
|
2612
2676
|
}
|
|
2613
|
-
function
|
|
2677
|
+
function sendRemoteFileUrlFailure(ws, requestId, sessionId, error, errorCode = ControlErrorCode.UNKNOWN) {
|
|
2678
|
+
ws.send(
|
|
2679
|
+
serializeControl({
|
|
2680
|
+
type: "remote_file_url_response",
|
|
2681
|
+
requestId,
|
|
2682
|
+
sessionId,
|
|
2683
|
+
success: false,
|
|
2684
|
+
error,
|
|
2685
|
+
errorCode
|
|
2686
|
+
})
|
|
2687
|
+
);
|
|
2688
|
+
}
|
|
2689
|
+
function sendRemoteFileUploadUrlFailure(ws, requestId, sessionId, error, errorCode = ControlErrorCode.UNKNOWN) {
|
|
2690
|
+
ws.send(
|
|
2691
|
+
serializeControl({
|
|
2692
|
+
type: "remote_file_upload_url_response",
|
|
2693
|
+
requestId,
|
|
2694
|
+
sessionId,
|
|
2695
|
+
success: false,
|
|
2696
|
+
error,
|
|
2697
|
+
errorCode
|
|
2698
|
+
})
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2701
|
+
function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, voiceProviders, remoteFileBridge, connectionInfo = {}) {
|
|
2614
2702
|
const clientWs = ws;
|
|
2615
2703
|
clientWs.isAlive = true;
|
|
2616
2704
|
registry.addClientWs(clientWs, connectionInfo);
|
|
@@ -2648,6 +2736,115 @@ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, v
|
|
|
2648
2736
|
handleRelayClientKick(clientWs, registry, logger, msg.requestId, msg.clientId);
|
|
2649
2737
|
return;
|
|
2650
2738
|
}
|
|
2739
|
+
if (msg.type === "remote_file_url_request") {
|
|
2740
|
+
if (!remoteFileBridge) {
|
|
2741
|
+
sendRemoteFileUrlFailure(clientWs, msg.requestId, msg.sessionId, "\u6587\u4EF6\u6D41\u670D\u52A1\u4E0D\u53EF\u7528");
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
if (!clientWs.clientId) {
|
|
2745
|
+
sendRemoteFileUrlFailure(
|
|
2746
|
+
clientWs,
|
|
2747
|
+
msg.requestId,
|
|
2748
|
+
msg.sessionId,
|
|
2749
|
+
"\u5BA2\u6237\u7AEF\u672A\u6CE8\u518C",
|
|
2750
|
+
ControlErrorCode.UNKNOWN
|
|
2751
|
+
);
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
const targetProxyId = clientWs.boundProxyId;
|
|
2755
|
+
if (!targetProxyId || !registry.isProxyOnline(targetProxyId)) {
|
|
2756
|
+
sendRemoteFileUrlFailure(
|
|
2757
|
+
clientWs,
|
|
2758
|
+
msg.requestId,
|
|
2759
|
+
msg.sessionId,
|
|
2760
|
+
"\u5F53\u524D\u672A\u8FDE\u63A5\u5F00\u53D1\u673A",
|
|
2761
|
+
ControlErrorCode.PROXY_OFFLINE
|
|
2762
|
+
);
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
const ownerProxyId = registry.getProxyForSession(msg.sessionId);
|
|
2766
|
+
if (ownerProxyId && ownerProxyId !== targetProxyId) {
|
|
2767
|
+
sendRemoteFileUrlFailure(
|
|
2768
|
+
clientWs,
|
|
2769
|
+
msg.requestId,
|
|
2770
|
+
msg.sessionId,
|
|
2771
|
+
"\u4F1A\u8BDD\u4E0D\u5C5E\u4E8E\u5F53\u524D\u5F00\u53D1\u673A",
|
|
2772
|
+
ControlErrorCode.SESSION_NOT_FOUND
|
|
2773
|
+
);
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
const { url, expiresAt } = remoteFileBridge.createUrl({
|
|
2777
|
+
clientId: clientWs.clientId,
|
|
2778
|
+
proxyId: targetProxyId,
|
|
2779
|
+
sessionId: msg.sessionId,
|
|
2780
|
+
path: msg.path,
|
|
2781
|
+
disposition: msg.disposition
|
|
2782
|
+
});
|
|
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
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
if (msg.type === "remote_file_upload_url_request") {
|
|
2797
|
+
if (!remoteFileBridge) {
|
|
2798
|
+
sendRemoteFileUploadUrlFailure(clientWs, msg.requestId, msg.sessionId, "\u4E0A\u4F20\u670D\u52A1\u4E0D\u53EF\u7528");
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
if (!clientWs.clientId) {
|
|
2802
|
+
sendRemoteFileUploadUrlFailure(clientWs, msg.requestId, msg.sessionId, "\u5BA2\u6237\u7AEF\u672A\u6CE8\u518C");
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
const targetProxyId = clientWs.boundProxyId;
|
|
2806
|
+
if (!targetProxyId || !registry.isProxyOnline(targetProxyId)) {
|
|
2807
|
+
sendRemoteFileUploadUrlFailure(
|
|
2808
|
+
clientWs,
|
|
2809
|
+
msg.requestId,
|
|
2810
|
+
msg.sessionId,
|
|
2811
|
+
"\u5F53\u524D\u672A\u8FDE\u63A5\u5F00\u53D1\u673A",
|
|
2812
|
+
ControlErrorCode.PROXY_OFFLINE
|
|
2813
|
+
);
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
const ownerProxyId = registry.getProxyForSession(msg.sessionId);
|
|
2817
|
+
if (ownerProxyId && ownerProxyId !== targetProxyId) {
|
|
2818
|
+
sendRemoteFileUploadUrlFailure(
|
|
2819
|
+
clientWs,
|
|
2820
|
+
msg.requestId,
|
|
2821
|
+
msg.sessionId,
|
|
2822
|
+
"\u4F1A\u8BDD\u4E0D\u5C5E\u4E8E\u5F53\u524D\u5F00\u53D1\u673A",
|
|
2823
|
+
ControlErrorCode.SESSION_NOT_FOUND
|
|
2824
|
+
);
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
const { uploadUrl, expiresAt } = remoteFileBridge.createUploadUrl({
|
|
2828
|
+
clientId: clientWs.clientId,
|
|
2829
|
+
proxyId: targetProxyId,
|
|
2830
|
+
sessionId: msg.sessionId,
|
|
2831
|
+
kind: msg.kind,
|
|
2832
|
+
fileName: msg.fileName,
|
|
2833
|
+
mimeType: msg.mimeType,
|
|
2834
|
+
size: msg.size
|
|
2835
|
+
});
|
|
2836
|
+
clientWs.send(
|
|
2837
|
+
serializeControl({
|
|
2838
|
+
type: "remote_file_upload_url_response",
|
|
2839
|
+
requestId: msg.requestId,
|
|
2840
|
+
sessionId: msg.sessionId,
|
|
2841
|
+
success: true,
|
|
2842
|
+
uploadUrl,
|
|
2843
|
+
expiresAt
|
|
2844
|
+
})
|
|
2845
|
+
);
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2651
2848
|
if (msg.type === "proxy_list_request") {
|
|
2652
2849
|
const proxies = registry.listProxiesWithName().map((p) => ({
|
|
2653
2850
|
...p,
|
|
@@ -3367,6 +3564,348 @@ function createVoiceProviderRegistry(adapters) {
|
|
|
3367
3564
|
};
|
|
3368
3565
|
}
|
|
3369
3566
|
|
|
3567
|
+
// src/remote-file-bridge.ts
|
|
3568
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
3569
|
+
import { WebSocket as WebSocket11 } from "ws";
|
|
3570
|
+
var REMOTE_FILE_TOKEN_TTL_MS = 5 * 60 * 1e3;
|
|
3571
|
+
var REMOTE_FILE_METADATA_TIMEOUT_MS = 30 * 1e3;
|
|
3572
|
+
var REMOTE_FILE_UPLOAD_RESPONSE_TIMEOUT_MS = 60 * 1e3;
|
|
3573
|
+
function encodeContentDisposition(disposition, fileName) {
|
|
3574
|
+
const type = disposition === "download" ? "attachment" : "inline";
|
|
3575
|
+
const fallback = fileName.replace(/["\\\r\n]/g, "_") || "download";
|
|
3576
|
+
return `${type}; filename="${fallback}"; filename*=UTF-8''${encodeURIComponent(fileName)}`;
|
|
3577
|
+
}
|
|
3578
|
+
function statusForErrorCode(errorCode) {
|
|
3579
|
+
switch (errorCode) {
|
|
3580
|
+
case ControlErrorCode.SESSION_NOT_FOUND:
|
|
3581
|
+
return 404;
|
|
3582
|
+
case ControlErrorCode.INVALID_PATH:
|
|
3583
|
+
return 400;
|
|
3584
|
+
case ControlErrorCode.PATH_ACCESS_DENIED:
|
|
3585
|
+
return 403;
|
|
3586
|
+
default:
|
|
3587
|
+
return 500;
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
var RemoteFileBridge = class {
|
|
3591
|
+
constructor(deps) {
|
|
3592
|
+
this.deps = deps;
|
|
3593
|
+
}
|
|
3594
|
+
deps;
|
|
3595
|
+
tokens = /* @__PURE__ */ new Map();
|
|
3596
|
+
uploadTokens = /* @__PURE__ */ new Map();
|
|
3597
|
+
pendingStreams = /* @__PURE__ */ new Map();
|
|
3598
|
+
pendingUploads = /* @__PURE__ */ new Map();
|
|
3599
|
+
createUrl(input) {
|
|
3600
|
+
this.cleanupExpiredTokens();
|
|
3601
|
+
const token = nanoid3(32);
|
|
3602
|
+
const expiresAt = Date.now() + REMOTE_FILE_TOKEN_TTL_MS;
|
|
3603
|
+
this.tokens.set(token, {
|
|
3604
|
+
token,
|
|
3605
|
+
clientId: input.clientId,
|
|
3606
|
+
proxyId: input.proxyId,
|
|
3607
|
+
sessionId: input.sessionId,
|
|
3608
|
+
path: input.path,
|
|
3609
|
+
disposition: input.disposition,
|
|
3610
|
+
expiresAt
|
|
3611
|
+
});
|
|
3612
|
+
return { url: `/api/remote-files/${token}`, expiresAt };
|
|
3613
|
+
}
|
|
3614
|
+
createUploadUrl(input) {
|
|
3615
|
+
this.cleanupExpiredTokens();
|
|
3616
|
+
const token = nanoid3(32);
|
|
3617
|
+
const expiresAt = Date.now() + REMOTE_FILE_TOKEN_TTL_MS;
|
|
3618
|
+
this.uploadTokens.set(token, {
|
|
3619
|
+
token,
|
|
3620
|
+
clientId: input.clientId,
|
|
3621
|
+
proxyId: input.proxyId,
|
|
3622
|
+
sessionId: input.sessionId,
|
|
3623
|
+
kind: input.kind,
|
|
3624
|
+
fileName: input.fileName,
|
|
3625
|
+
mimeType: input.mimeType,
|
|
3626
|
+
size: input.size,
|
|
3627
|
+
expiresAt
|
|
3628
|
+
});
|
|
3629
|
+
return { uploadUrl: `/api/remote-uploads/${token}`, expiresAt };
|
|
3630
|
+
}
|
|
3631
|
+
handleHttpRequest(req, res) {
|
|
3632
|
+
const tokenValue = req.params.token;
|
|
3633
|
+
const token = typeof tokenValue === "string" ? this.tokens.get(tokenValue) : void 0;
|
|
3634
|
+
if (!token || token.expiresAt <= Date.now()) {
|
|
3635
|
+
if (typeof tokenValue === "string") this.tokens.delete(tokenValue);
|
|
3636
|
+
res.status(404).json({ error: "remote_file_url_expired" });
|
|
3637
|
+
return;
|
|
3638
|
+
}
|
|
3639
|
+
const proxyWs = this.deps.registry.getProxy(token.proxyId);
|
|
3640
|
+
if (!proxyWs || proxyWs.readyState !== WebSocket11.OPEN) {
|
|
3641
|
+
res.status(502).json({ error: "proxy_offline" });
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
const streamId = nanoid3(21);
|
|
3645
|
+
const metadataTimer = setTimeout(() => {
|
|
3646
|
+
this.failStream(streamId, 504, "\u8BFB\u53D6\u6587\u4EF6\u5143\u6570\u636E\u8D85\u65F6");
|
|
3647
|
+
}, REMOTE_FILE_METADATA_TIMEOUT_MS);
|
|
3648
|
+
const pending = {
|
|
3649
|
+
streamId,
|
|
3650
|
+
token,
|
|
3651
|
+
res,
|
|
3652
|
+
metadataTimer,
|
|
3653
|
+
headersSent: false,
|
|
3654
|
+
finished: false
|
|
3655
|
+
};
|
|
3656
|
+
this.pendingStreams.set(streamId, pending);
|
|
3657
|
+
res.on("close", () => {
|
|
3658
|
+
if (pending.finished) return;
|
|
3659
|
+
pending.finished = true;
|
|
3660
|
+
this.pendingStreams.delete(streamId);
|
|
3661
|
+
clearTimeout(metadataTimer);
|
|
3662
|
+
this.sendCancel(token.proxyId, streamId);
|
|
3663
|
+
this.deps.logger.info({ streamId, proxyId: token.proxyId }, "Remote file HTTP stream closed");
|
|
3664
|
+
});
|
|
3665
|
+
proxyWs.send(
|
|
3666
|
+
serializeControl({
|
|
3667
|
+
type: "remote_file_stream_request",
|
|
3668
|
+
streamId,
|
|
3669
|
+
sessionId: token.sessionId,
|
|
3670
|
+
path: token.path,
|
|
3671
|
+
disposition: token.disposition
|
|
3672
|
+
})
|
|
3673
|
+
);
|
|
3674
|
+
this.deps.logger.info(
|
|
3675
|
+
{ streamId, proxyId: token.proxyId, sessionId: token.sessionId, path: token.path },
|
|
3676
|
+
"Remote file HTTP stream requested"
|
|
3677
|
+
);
|
|
3678
|
+
}
|
|
3679
|
+
handleUploadHttpRequest(req, res) {
|
|
3680
|
+
const tokenValue = req.params.token;
|
|
3681
|
+
const token = typeof tokenValue === "string" ? this.uploadTokens.get(tokenValue) : void 0;
|
|
3682
|
+
if (!token || token.expiresAt <= Date.now()) {
|
|
3683
|
+
if (typeof tokenValue === "string") this.uploadTokens.delete(tokenValue);
|
|
3684
|
+
res.status(404).json({ error: "remote_file_upload_url_expired" });
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3687
|
+
const proxyWs = this.deps.registry.getProxy(token.proxyId);
|
|
3688
|
+
if (!proxyWs || proxyWs.readyState !== WebSocket11.OPEN) {
|
|
3689
|
+
res.status(502).json({ error: "proxy_offline" });
|
|
3690
|
+
return;
|
|
3691
|
+
}
|
|
3692
|
+
const uploadId = nanoid3(21);
|
|
3693
|
+
const pending = {
|
|
3694
|
+
uploadId,
|
|
3695
|
+
token,
|
|
3696
|
+
res,
|
|
3697
|
+
chunkSeq: 0,
|
|
3698
|
+
requestEnded: false,
|
|
3699
|
+
finished: false,
|
|
3700
|
+
responseTimer: null
|
|
3701
|
+
};
|
|
3702
|
+
this.pendingUploads.set(uploadId, pending);
|
|
3703
|
+
proxyWs.send(
|
|
3704
|
+
serializeControl({
|
|
3705
|
+
type: "remote_file_upload_stream_request",
|
|
3706
|
+
uploadId,
|
|
3707
|
+
sessionId: token.sessionId,
|
|
3708
|
+
kind: token.kind,
|
|
3709
|
+
mimeType: token.mimeType,
|
|
3710
|
+
...token.fileName ? { fileName: token.fileName } : {},
|
|
3711
|
+
...token.size !== void 0 ? { size: token.size } : {}
|
|
3712
|
+
})
|
|
3713
|
+
);
|
|
3714
|
+
req.on("data", (chunk) => {
|
|
3715
|
+
if (pending.finished) return;
|
|
3716
|
+
req.pause();
|
|
3717
|
+
const frame = encodeFileStreamFrame(uploadId, pending.chunkSeq, Buffer.from(chunk));
|
|
3718
|
+
pending.chunkSeq += 1;
|
|
3719
|
+
proxyWs.send(frame, { binary: true }, (err) => {
|
|
3720
|
+
if (err) {
|
|
3721
|
+
this.failUpload(uploadId, 502, err.message);
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3724
|
+
req.resume();
|
|
3725
|
+
});
|
|
3726
|
+
});
|
|
3727
|
+
req.on("end", () => {
|
|
3728
|
+
if (pending.finished) return;
|
|
3729
|
+
pending.requestEnded = true;
|
|
3730
|
+
pending.responseTimer = setTimeout(() => {
|
|
3731
|
+
this.failUpload(uploadId, 504, "\u7B49\u5F85\u4E0A\u4F20\u5199\u5165\u7ED3\u679C\u8D85\u65F6");
|
|
3732
|
+
}, REMOTE_FILE_UPLOAD_RESPONSE_TIMEOUT_MS);
|
|
3733
|
+
proxyWs.send(serializeControl({ type: "remote_file_upload_stream_complete", uploadId }));
|
|
3734
|
+
});
|
|
3735
|
+
req.on("error", (err) => {
|
|
3736
|
+
this.failUpload(uploadId, 400, err.message);
|
|
3737
|
+
});
|
|
3738
|
+
req.on("close", () => {
|
|
3739
|
+
if (pending.finished || pending.requestEnded) return;
|
|
3740
|
+
this.failUpload(uploadId, 499, "\u4E0A\u4F20\u8BF7\u6C42\u5DF2\u65AD\u5F00");
|
|
3741
|
+
});
|
|
3742
|
+
this.deps.logger.info(
|
|
3743
|
+
{ uploadId, proxyId: token.proxyId, sessionId: token.sessionId, kind: token.kind },
|
|
3744
|
+
"Remote file HTTP upload requested"
|
|
3745
|
+
);
|
|
3746
|
+
}
|
|
3747
|
+
handleProxyControl(proxyId, msg) {
|
|
3748
|
+
if (msg.type === "remote_file_upload_stream_response") {
|
|
3749
|
+
return this.handleUploadResponse(proxyId, msg);
|
|
3750
|
+
}
|
|
3751
|
+
const pending = this.pendingStreams.get(msg.streamId);
|
|
3752
|
+
if (!pending) return true;
|
|
3753
|
+
if (pending.token.proxyId !== proxyId) {
|
|
3754
|
+
this.deps.logger.warn(
|
|
3755
|
+
{ streamId: msg.streamId, expectedProxyId: pending.token.proxyId, proxyId },
|
|
3756
|
+
"Remote file stream control ignored: proxy mismatch"
|
|
3757
|
+
);
|
|
3758
|
+
return true;
|
|
3759
|
+
}
|
|
3760
|
+
if (msg.type === "remote_file_stream_response") {
|
|
3761
|
+
this.handleStreamResponse(pending, msg);
|
|
3762
|
+
return true;
|
|
3763
|
+
}
|
|
3764
|
+
this.handleStreamComplete(pending, msg);
|
|
3765
|
+
return true;
|
|
3766
|
+
}
|
|
3767
|
+
handleProxyBinary(proxyId, data) {
|
|
3768
|
+
const decoded = decodeFileStreamFrame(data);
|
|
3769
|
+
if (!decoded) return false;
|
|
3770
|
+
const pending = this.pendingStreams.get(decoded.streamId);
|
|
3771
|
+
if (!pending) return true;
|
|
3772
|
+
if (pending.token.proxyId !== proxyId) {
|
|
3773
|
+
this.deps.logger.warn(
|
|
3774
|
+
{ streamId: decoded.streamId, expectedProxyId: pending.token.proxyId, proxyId },
|
|
3775
|
+
"Remote file stream chunk ignored: proxy mismatch"
|
|
3776
|
+
);
|
|
3777
|
+
return true;
|
|
3778
|
+
}
|
|
3779
|
+
if (!pending.headersSent || pending.finished) return true;
|
|
3780
|
+
pending.res.write(Buffer.from(decoded.data));
|
|
3781
|
+
return true;
|
|
3782
|
+
}
|
|
3783
|
+
handleStreamResponse(pending, msg) {
|
|
3784
|
+
clearTimeout(pending.metadataTimer);
|
|
3785
|
+
if (!msg.success) {
|
|
3786
|
+
this.failStream(
|
|
3787
|
+
pending.streamId,
|
|
3788
|
+
statusForErrorCode(msg.errorCode),
|
|
3789
|
+
msg.error ?? "\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25",
|
|
3790
|
+
msg.errorCode
|
|
3791
|
+
);
|
|
3792
|
+
return;
|
|
3793
|
+
}
|
|
3794
|
+
pending.headersSent = true;
|
|
3795
|
+
pending.res.status(200);
|
|
3796
|
+
pending.res.setHeader("Content-Type", msg.mimeType ?? "application/octet-stream");
|
|
3797
|
+
pending.res.setHeader("Cache-Control", "no-store");
|
|
3798
|
+
pending.res.setHeader("X-Content-Type-Options", "nosniff");
|
|
3799
|
+
if (msg.size !== void 0) {
|
|
3800
|
+
pending.res.setHeader("Content-Length", String(msg.size));
|
|
3801
|
+
}
|
|
3802
|
+
pending.res.setHeader(
|
|
3803
|
+
"Content-Disposition",
|
|
3804
|
+
encodeContentDisposition(pending.token.disposition, msg.fileName ?? "download")
|
|
3805
|
+
);
|
|
3806
|
+
pending.res.flushHeaders();
|
|
3807
|
+
}
|
|
3808
|
+
handleStreamComplete(pending, msg) {
|
|
3809
|
+
clearTimeout(pending.metadataTimer);
|
|
3810
|
+
pending.finished = true;
|
|
3811
|
+
this.pendingStreams.delete(pending.streamId);
|
|
3812
|
+
if (!msg.success) {
|
|
3813
|
+
if (!pending.headersSent) {
|
|
3814
|
+
this.writeJsonError(
|
|
3815
|
+
pending.res,
|
|
3816
|
+
statusForErrorCode(msg.errorCode),
|
|
3817
|
+
msg.error ?? "\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25",
|
|
3818
|
+
msg.errorCode
|
|
3819
|
+
);
|
|
3820
|
+
} else {
|
|
3821
|
+
pending.res.destroy(new Error(msg.error ?? "Remote file stream failed"));
|
|
3822
|
+
}
|
|
3823
|
+
return;
|
|
3824
|
+
}
|
|
3825
|
+
pending.res.end();
|
|
3826
|
+
}
|
|
3827
|
+
handleUploadResponse(proxyId, msg) {
|
|
3828
|
+
const pending = this.pendingUploads.get(msg.uploadId);
|
|
3829
|
+
if (!pending) return true;
|
|
3830
|
+
if (pending.token.proxyId !== proxyId) {
|
|
3831
|
+
this.deps.logger.warn(
|
|
3832
|
+
{ uploadId: msg.uploadId, expectedProxyId: pending.token.proxyId, proxyId },
|
|
3833
|
+
"Remote file upload response ignored: proxy mismatch"
|
|
3834
|
+
);
|
|
3835
|
+
return true;
|
|
3836
|
+
}
|
|
3837
|
+
pending.finished = true;
|
|
3838
|
+
this.pendingUploads.delete(msg.uploadId);
|
|
3839
|
+
if (pending.responseTimer) clearTimeout(pending.responseTimer);
|
|
3840
|
+
if (!msg.success) {
|
|
3841
|
+
this.writeJsonError(
|
|
3842
|
+
pending.res,
|
|
3843
|
+
statusForErrorCode(msg.errorCode),
|
|
3844
|
+
msg.error ?? "\u4E0A\u4F20\u6587\u4EF6\u5931\u8D25",
|
|
3845
|
+
msg.errorCode
|
|
3846
|
+
);
|
|
3847
|
+
return true;
|
|
3848
|
+
}
|
|
3849
|
+
pending.res.status(200).json({
|
|
3850
|
+
sessionId: msg.sessionId,
|
|
3851
|
+
success: true,
|
|
3852
|
+
path: msg.path
|
|
3853
|
+
});
|
|
3854
|
+
return true;
|
|
3855
|
+
}
|
|
3856
|
+
failStream(streamId, status, error, errorCode) {
|
|
3857
|
+
const pending = this.pendingStreams.get(streamId);
|
|
3858
|
+
if (!pending) return;
|
|
3859
|
+
pending.finished = true;
|
|
3860
|
+
this.pendingStreams.delete(streamId);
|
|
3861
|
+
clearTimeout(pending.metadataTimer);
|
|
3862
|
+
this.sendCancel(pending.token.proxyId, streamId);
|
|
3863
|
+
if (!pending.headersSent) {
|
|
3864
|
+
this.writeJsonError(pending.res, status, error, errorCode);
|
|
3865
|
+
} else {
|
|
3866
|
+
pending.res.destroy(new Error(error));
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
failUpload(uploadId, status, error, errorCode) {
|
|
3870
|
+
const pending = this.pendingUploads.get(uploadId);
|
|
3871
|
+
if (!pending) return;
|
|
3872
|
+
pending.finished = true;
|
|
3873
|
+
this.pendingUploads.delete(uploadId);
|
|
3874
|
+
if (pending.responseTimer) clearTimeout(pending.responseTimer);
|
|
3875
|
+
this.sendUploadCancel(pending.token.proxyId, uploadId);
|
|
3876
|
+
if (!pending.res.headersSent) {
|
|
3877
|
+
this.writeJsonError(pending.res, status, error, errorCode);
|
|
3878
|
+
} else {
|
|
3879
|
+
pending.res.destroy(new Error(error));
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
writeJsonError(res, status, error, errorCode) {
|
|
3883
|
+
res.status(status).json({
|
|
3884
|
+
error,
|
|
3885
|
+
...errorCode ? { errorCode } : {}
|
|
3886
|
+
});
|
|
3887
|
+
}
|
|
3888
|
+
sendCancel(proxyId, streamId) {
|
|
3889
|
+
const proxyWs = this.deps.registry.getProxy(proxyId);
|
|
3890
|
+
if (!proxyWs || proxyWs.readyState !== WebSocket11.OPEN) return;
|
|
3891
|
+
proxyWs.send(serializeControl({ type: "remote_file_stream_cancel", streamId }));
|
|
3892
|
+
}
|
|
3893
|
+
sendUploadCancel(proxyId, uploadId) {
|
|
3894
|
+
const proxyWs = this.deps.registry.getProxy(proxyId);
|
|
3895
|
+
if (!proxyWs || proxyWs.readyState !== WebSocket11.OPEN) return;
|
|
3896
|
+
proxyWs.send(serializeControl({ type: "remote_file_upload_stream_cancel", uploadId }));
|
|
3897
|
+
}
|
|
3898
|
+
cleanupExpiredTokens() {
|
|
3899
|
+
const now = Date.now();
|
|
3900
|
+
for (const [token, meta] of this.tokens) {
|
|
3901
|
+
if (meta.expiresAt <= now) this.tokens.delete(token);
|
|
3902
|
+
}
|
|
3903
|
+
for (const [token, meta] of this.uploadTokens) {
|
|
3904
|
+
if (meta.expiresAt <= now) this.uploadTokens.delete(token);
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
};
|
|
3908
|
+
|
|
3370
3909
|
// src/server.ts
|
|
3371
3910
|
var MODULE_DIR = dirname3(fileURLToPath2(import.meta.url));
|
|
3372
3911
|
var PACKAGED_FONTS_DIR = resolve(MODULE_DIR, "../assets/fonts");
|
|
@@ -3399,6 +3938,7 @@ function createRelayServer(options) {
|
|
|
3399
3938
|
);
|
|
3400
3939
|
}
|
|
3401
3940
|
const registry = new RelayRegistry();
|
|
3941
|
+
const remoteFileBridge = new RemoteFileBridge({ registry, logger });
|
|
3402
3942
|
const voiceConfigStore = createVoiceConfigStore({
|
|
3403
3943
|
dataDir,
|
|
3404
3944
|
defaults: options.voiceDefaults
|
|
@@ -3455,6 +3995,12 @@ function createRelayServer(options) {
|
|
|
3455
3995
|
getClientToken: () => clientToken ?? null
|
|
3456
3996
|
})
|
|
3457
3997
|
);
|
|
3998
|
+
app.get("/api/remote-files/:token", (req, res) => {
|
|
3999
|
+
remoteFileBridge.handleHttpRequest(req, res);
|
|
4000
|
+
});
|
|
4001
|
+
app.put("/api/remote-uploads/:token", (req, res) => {
|
|
4002
|
+
remoteFileBridge.handleUploadHttpRequest(req, res);
|
|
4003
|
+
});
|
|
3458
4004
|
const httpServer = createServer(app);
|
|
3459
4005
|
const proxyWss = new WebSocketServer({ noServer: true });
|
|
3460
4006
|
const clientWss = new WebSocketServer({ noServer: true });
|
|
@@ -3522,13 +4068,22 @@ function createRelayServer(options) {
|
|
|
3522
4068
|
socket.destroy();
|
|
3523
4069
|
});
|
|
3524
4070
|
proxyWss.on("connection", (ws) => {
|
|
3525
|
-
handleProxyConnection(ws, registry, logger, relayChaos);
|
|
4071
|
+
handleProxyConnection(ws, registry, logger, relayChaos, remoteFileBridge);
|
|
3526
4072
|
});
|
|
3527
4073
|
clientWss.on("connection", (ws, request) => {
|
|
3528
|
-
handleClientConnection(
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
4074
|
+
handleClientConnection(
|
|
4075
|
+
ws,
|
|
4076
|
+
registry,
|
|
4077
|
+
logger,
|
|
4078
|
+
relayChaos,
|
|
4079
|
+
voiceConfigStore,
|
|
4080
|
+
voiceProviders,
|
|
4081
|
+
remoteFileBridge,
|
|
4082
|
+
{
|
|
4083
|
+
userAgent: firstHeaderValue(request.headers["user-agent"]),
|
|
4084
|
+
remoteAddress: requestRemoteAddress(request)
|
|
4085
|
+
}
|
|
4086
|
+
);
|
|
3532
4087
|
});
|
|
3533
4088
|
voiceAsrWss.on("connection", (ws) => {
|
|
3534
4089
|
handleVoiceAsrConnection(ws, voiceConfigStore, logger, voiceProviders);
|
|
@@ -3598,4 +4153,4 @@ export {
|
|
|
3598
4153
|
parseRelayChaosFromEnv,
|
|
3599
4154
|
createRelayServer
|
|
3600
4155
|
};
|
|
3601
|
-
//# sourceMappingURL=chunk-
|
|
4156
|
+
//# sourceMappingURL=chunk-XY4ATTV2.js.map
|