termdock 1.4.341 → 1.4.342
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/THIRD_PARTY_NOTICES.md +79 -0
- package/dist/client/assets/App-DuWNc_Sd.js +187 -0
- package/dist/client/assets/App-DuWNc_Sd.js.br +0 -0
- package/dist/client/assets/App-DuWNc_Sd.js.gz +0 -0
- package/dist/client/assets/{ArchitecturePanel-Dz0hgSdw.js → ArchitecturePanel-Bjhd5uMf.js} +2 -2
- package/dist/client/assets/ArchitecturePanel-Bjhd5uMf.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-Bjhd5uMf.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-3PpmA31G.js → ChangeWalkthroughPanel-DKDNcKro.js} +1 -1
- package/dist/client/assets/ChangeWalkthroughPanel-DKDNcKro.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DKDNcKro.js.gz +0 -0
- package/dist/client/assets/{CollaborationMainWorkspace-QA7cyrcg.js → CollaborationMainWorkspace-C11Rr6yD.js} +1 -1
- package/dist/client/assets/CollaborationMainWorkspace-C11Rr6yD.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-C11Rr6yD.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-DNRyAkOX.js +2 -0
- package/dist/client/assets/{ComputerControlView-DfKdSvrs.js → ComputerControlView-B5C6ETMh.js} +3 -3
- package/dist/client/assets/ComputerControlView-B5C6ETMh.js.br +0 -0
- package/dist/client/assets/ComputerControlView-B5C6ETMh.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-IFr1ujrl.js → DagPlayground-BrrzhUzm.js} +1 -1
- package/dist/client/assets/DagPlayground-BrrzhUzm.js.br +0 -0
- package/dist/client/assets/DagPlayground-BrrzhUzm.js.gz +0 -0
- package/dist/client/assets/{DiffLab-BSQmv0td.js → DiffLab-DdFwfYKr.js} +1 -1
- package/dist/client/assets/DiffLab-DdFwfYKr.js.br +0 -0
- package/dist/client/assets/DiffLab-DdFwfYKr.js.gz +0 -0
- package/dist/client/assets/{DiffReview-CfHl0jeF.js → DiffReview-GloQdQAq.js} +1 -1
- package/dist/client/assets/DiffReview-GloQdQAq.js.br +0 -0
- package/dist/client/assets/DiffReview-GloQdQAq.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-B_zSSqde.js → DiffReviewLab-C5_nDmPj.js} +1 -1
- package/dist/client/assets/DiffReviewLab-C5_nDmPj.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-C5_nDmPj.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-DORR0BUb.js → DiffViewer-lsURzN_a.js} +1 -1
- package/dist/client/assets/DiffViewer-lsURzN_a.js.br +0 -0
- package/dist/client/assets/DiffViewer-lsURzN_a.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-QdScJljR.js → ModelPreview-Dha8K529.js} +1 -1
- package/dist/client/assets/ModelPreview-Dha8K529.js.br +0 -0
- package/dist/client/assets/ModelPreview-Dha8K529.js.gz +0 -0
- package/dist/client/assets/{RightSidebar-nUsJCrB9.js → RightSidebar-DFWCFBCy.js} +3 -3
- package/dist/client/assets/RightSidebar-DFWCFBCy.js.br +0 -0
- package/dist/client/assets/RightSidebar-DFWCFBCy.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-B51uxXBg.js → abnfDiagram-VCTEODGH-3R9vxuaa.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-3R9vxuaa.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-3R9vxuaa.js.gz +0 -0
- package/dist/client/assets/app-By1Q_guu.js +35 -0
- package/dist/client/assets/app-By1Q_guu.js.br +0 -0
- package/dist/client/assets/app-By1Q_guu.js.gz +0 -0
- package/dist/client/assets/{arc-Bkn10vSK.js → arc-BRSYFA6a.js} +1 -1
- package/dist/client/assets/arc-BRSYFA6a.js.br +0 -0
- package/dist/client/assets/arc-BRSYFA6a.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-CK-cIr48.js → architectureDiagram-5GKGNRK7-DU21Xuxy.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DU21Xuxy.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DU21Xuxy.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-DzbWXcDy.js → blockDiagram-I7D4REHJ-B8aQKSok.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-B8aQKSok.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-B8aQKSok.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-rbi8aUz6.js → c4Diagram-7LVT6UL2-CSoL6w1j.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-CSoL6w1j.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-CSoL6w1j.js.gz +0 -0
- package/dist/client/assets/channel-CQrgntwz.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CopzhZr-.js → chunk-2Q5K7J3B-BdEnYHBX.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-ClLaQzHk.js → chunk-5VM5RSS4-p7idUc56.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-BSC7rK0l.js → chunk-F27PBJKO-C-jRH04G.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-C-jRH04G.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-C-jRH04G.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-B2VXoEhN.js → chunk-IMKFNOWR-D_GfyMqI.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-D_GfyMqI.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-D_GfyMqI.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-BqOinZdA.js → chunk-JWPE2WC7-C_tTJeyp.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-CNE45B-7.js → chunk-POPQ4Y6H-B7jDpCMg.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-f0h2wclt.js → chunk-SVP7TREG-D2bP2OpS.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-D2bP2OpS.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D2bP2OpS.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-BLYE27kR.js → chunk-TICWLB2K-BCmzEINw.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-BCmzEINw.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BCmzEINw.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-CVDDfXPc.js → chunk-XXDRQBXY-CU7zj0sD.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-S0RVBlYQ.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-S0RVBlYQ.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-64n9-rjs.js → cose-bilkent-JH36ORCC-Cc6zJb8W.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Cc6zJb8W.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-Cc6zJb8W.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-BQu4qBBj.js → cynefin-OW5HDTMX-Dx8-oZ-5.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-Dx8-oZ-5.js.br +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-BQu4qBBj.js.gz → cynefin-OW5HDTMX-Dx8-oZ-5.js.gz} +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BnwaLoUi.js → cynefinDiagram-5FMLGOSQ-D8w9AR9E.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-D8w9AR9E.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-D8w9AR9E.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-CqFJh0CI.js → dagre-GXQ25YYZ-DWu5zqlw.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-DWu5zqlw.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-DWu5zqlw.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-CbPoBysM.js → diagram-S7CK7UJ4-C8P4HVUJ.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-C8P4HVUJ.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-C8P4HVUJ.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-BhBN4F33.js → diagram-UQ7AKVKN-B_7uz1pF.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-B_7uz1pF.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-B_7uz1pF.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-eYeeGcK9.js → diagram-VSXAHHWV-CtUxXdZO.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-CtUxXdZO.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CtUxXdZO.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-BJzhPV4h.js → diagram-VX7I27RA-qu13Uscy.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-qu13Uscy.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-qu13Uscy.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-CnjpNAJH.js → diagram-Z3DM3KII-CBK_SCwI.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-CBK_SCwI.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CBK_SCwI.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-BSJQBTtu.js → ebnfDiagram-PWID7BFC-WO-4_pvg.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-WO-4_pvg.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-WO-4_pvg.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DJ-J9MxE.js → erDiagram-RLTQ6QDP-CJBTe-Vf.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-CJBTe-Vf.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-CJBTe-Vf.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-BoQNTthC.js → flowDiagram-HODETNUW-BMB8WufZ.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-BMB8WufZ.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BMB8WufZ.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CFE98nzY.js → ganttDiagram-EL5Y4UJY-UU86YYND.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-UU86YYND.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-UU86YYND.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-C_3fpCXt.js → gestureArbiter-B1YQAqce.js} +1 -1
- package/dist/client/assets/gestureArbiter-B1YQAqce.js.br +0 -0
- package/dist/client/assets/gestureArbiter-B1YQAqce.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-DxJzGDIE.js → gitGraphDiagram-WWUBYQGX-C_bQTzYa.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C_bQTzYa.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C_bQTzYa.js.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-BZk87iJj.js → infoDiagram-27XIBGKW-D-UPLFB4.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-DVsj1R9s.js → ishikawaDiagram-5VMMS53U-BoXlttHi.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BoXlttHi.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BoXlttHi.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-BAB2AWNl.js → journeyDiagram-3NMN7TZE-CFFrNcIl.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CFFrNcIl.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CFFrNcIl.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-HPNzrFVI.js → kanban-definition-UXKFOSKX-CVGuqvHc.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-CVGuqvHc.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CVGuqvHc.js.gz +0 -0
- package/dist/client/assets/{layout-bA7IPCWx.js → layout-3D_JBmoW.js} +1 -1
- package/dist/client/assets/layout-3D_JBmoW.js.br +0 -0
- package/dist/client/assets/layout-3D_JBmoW.js.gz +0 -0
- package/dist/client/assets/{linear-Cd102271.js → linear-DSCyDRIV.js} +1 -1
- package/dist/client/assets/linear-DSCyDRIV.js.br +0 -0
- package/dist/client/assets/linear-DSCyDRIV.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-C4wd6qUT.js → mermaid.core-DmLvRjSp.js} +5 -5
- package/dist/client/assets/mermaid.core-DmLvRjSp.js.br +0 -0
- package/dist/client/assets/mermaid.core-DmLvRjSp.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-DMxZDI3z.js → mindmap-definition-YA3MSWOX-D-Doz5kr.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D-Doz5kr.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D-Doz5kr.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-aU8HdPsL.js → passwordBootstrap-DzqP6851.js} +1 -1
- package/dist/client/assets/passwordBootstrap-DzqP6851.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-DzqP6851.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-BliPfrj_.js → pegDiagram-XKGWAZYB-jqnn8PjR.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-jqnn8PjR.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-jqnn8PjR.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-esWyI6AY.js → pieDiagram-E7YTZNPT-D4MedGxH.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-D4MedGxH.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-D4MedGxH.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-CBoaEIHy.js → quadrantDiagram-AXDQQJYC-C4lTdb_9.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-C4lTdb_9.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-C4lTdb_9.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-FB5bPmYm.js → railroadDiagram-O6MQD6OU-KLdIDLQO.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-KLdIDLQO.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-KLdIDLQO.js.gz +0 -0
- package/dist/client/assets/{rdpSession-DnBdxkm1.js → rdpSession-QckpThDq.js} +1 -1
- package/dist/client/assets/rdpSession-QckpThDq.js.br +0 -0
- package/dist/client/assets/rdpSession-QckpThDq.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-DDmic9E5.js → referenceLongPress-ErqUYlIw.js} +1 -1
- package/dist/client/assets/referenceLongPress-ErqUYlIw.js.br +0 -0
- package/dist/client/assets/referenceLongPress-ErqUYlIw.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-CpokwhJY.js → requirementDiagram-BXWQKSXE-BEeCbvLt.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BEeCbvLt.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BEeCbvLt.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-DXQDOMAh.js → sankeyDiagram-P5KCCOFB-DD7In9QJ.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DD7In9QJ.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DD7In9QJ.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-Dn3ZooAM.js → sequenceDiagram-WJ2MYXX4-BCeJR1Wm.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BCeJR1Wm.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BCeJR1Wm.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-PItE9Y1o.js → sizeCapture-INFHLROL-DO250O3y.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-DO250O3y.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-DO250O3y.js.gz +0 -0
- package/dist/client/assets/{sparkles-BwY5qEOv.js → sparkles-DiUUcfQp.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-BBZ01SXU.js → stateDiagram-D77RDMKH-j-Rx3MIu.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-j-Rx3MIu.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-j-Rx3MIu.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-80TmHBA3.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-BY8IlbkR.js → swimlanes-42K2YHIH-D8BxfTs0.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-D8BxfTs0.js.br +0 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-BY8IlbkR.js.gz → swimlanes-42K2YHIH-D8BxfTs0.js.gz} +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CDwZ9Ews.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-DyY9g2Hx.js → timeline-definition-24CTP7MA-Q26Gi8XT.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-Q26Gi8XT.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-Q26Gi8XT.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-qb3d0Svx.js → triangle-alert-B3htKRDS.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-Dy0PQ4-U.js → vennDiagram-4TSXK5OY-QCEPdOun.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-QCEPdOun.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-QCEPdOun.js.gz +0 -0
- package/dist/client/assets/{videoCompression-BDNHhfKS.js → videoCompression-DomMr6dv.js} +1 -1
- package/dist/client/assets/videoCompression-DomMr6dv.js.br +0 -0
- package/dist/client/assets/videoCompression-DomMr6dv.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DtBEPD3N.js → wardleyDiagram-VM6X3IG4-B6J6Og35.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-B6J6Og35.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-B6J6Og35.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-B2K1XjDQ.js → xychartDiagram-S5SC5T6Z-pdImapP4.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-pdImapP4.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-pdImapP4.js.gz +0 -0
- package/dist/client/index.html +1 -1
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/client/sw-encrypted-background.js +1 -1
- package/dist/client/sw-encrypted-background.js.br +0 -0
- package/dist/client/sw-encrypted-background.js.gz +0 -0
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.br +0 -0
- package/dist/client/sw.js.gz +0 -0
- package/dist/server/entry.js +29 -20
- package/dist/server/federation/binaryPacket.js +28 -0
- package/dist/server/federation/packets.js +53 -7
- package/dist/server/federation/recordCompression.js +59 -0
- package/dist/server/federation/runtime.js +35 -37
- package/dist/server/federation/streamDuplex.js +62 -0
- package/dist/server/federation/transferWindow.js +50 -0
- package/dist/server/federation/webTransportCertificate.js +52 -0
- package/dist/server/federation/webTransportProtocol.js +25 -0
- package/dist/server/federation/webTransportServer.js +142 -0
- package/package.json +7 -1
- package/runtime-manifest.json +4 -4
- package/dist/client/assets/App-Bs90VDwF.js +0 -187
- package/dist/client/assets/App-Bs90VDwF.js.br +0 -0
- package/dist/client/assets/App-Bs90VDwF.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-Dz0hgSdw.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-Dz0hgSdw.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-3PpmA31G.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-3PpmA31G.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-QA7cyrcg.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-QA7cyrcg.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-Bjkxb7UF.js +0 -2
- package/dist/client/assets/ComputerControlView-DfKdSvrs.js.br +0 -0
- package/dist/client/assets/ComputerControlView-DfKdSvrs.js.gz +0 -0
- package/dist/client/assets/DagPlayground-IFr1ujrl.js.br +0 -0
- package/dist/client/assets/DagPlayground-IFr1ujrl.js.gz +0 -0
- package/dist/client/assets/DiffLab-BSQmv0td.js.br +0 -0
- package/dist/client/assets/DiffLab-BSQmv0td.js.gz +0 -0
- package/dist/client/assets/DiffReview-CfHl0jeF.js.br +0 -0
- package/dist/client/assets/DiffReview-CfHl0jeF.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-B_zSSqde.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-B_zSSqde.js.gz +0 -0
- package/dist/client/assets/DiffViewer-DORR0BUb.js.br +0 -0
- package/dist/client/assets/DiffViewer-DORR0BUb.js.gz +0 -0
- package/dist/client/assets/ModelPreview-QdScJljR.js.br +0 -0
- package/dist/client/assets/ModelPreview-QdScJljR.js.gz +0 -0
- package/dist/client/assets/RightSidebar-nUsJCrB9.js.br +0 -0
- package/dist/client/assets/RightSidebar-nUsJCrB9.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-B51uxXBg.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-B51uxXBg.js.gz +0 -0
- package/dist/client/assets/app-DIurxlSe.js +0 -35
- package/dist/client/assets/app-DIurxlSe.js.br +0 -0
- package/dist/client/assets/app-DIurxlSe.js.gz +0 -0
- package/dist/client/assets/arc-Bkn10vSK.js.br +0 -0
- package/dist/client/assets/arc-Bkn10vSK.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CK-cIr48.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CK-cIr48.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DzbWXcDy.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DzbWXcDy.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-rbi8aUz6.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-rbi8aUz6.js.gz +0 -0
- package/dist/client/assets/channel-DWD77N0J.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-BSC7rK0l.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BSC7rK0l.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-B2VXoEhN.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-B2VXoEhN.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-f0h2wclt.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-f0h2wclt.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BLYE27kR.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BLYE27kR.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-BPUOP6TD.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BPUOP6TD.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-64n9-rjs.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-64n9-rjs.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BQu4qBBj.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BnwaLoUi.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BnwaLoUi.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CqFJh0CI.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CqFJh0CI.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CbPoBysM.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CbPoBysM.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BhBN4F33.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BhBN4F33.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-eYeeGcK9.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-eYeeGcK9.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-BJzhPV4h.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-BJzhPV4h.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CnjpNAJH.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CnjpNAJH.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BSJQBTtu.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BSJQBTtu.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DJ-J9MxE.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DJ-J9MxE.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BoQNTthC.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BoQNTthC.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CFE98nzY.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CFE98nzY.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-C_3fpCXt.js.br +0 -0
- package/dist/client/assets/gestureArbiter-C_3fpCXt.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DxJzGDIE.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DxJzGDIE.js.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DVsj1R9s.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DVsj1R9s.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BAB2AWNl.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BAB2AWNl.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-HPNzrFVI.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-HPNzrFVI.js.gz +0 -0
- package/dist/client/assets/layout-bA7IPCWx.js.br +0 -0
- package/dist/client/assets/layout-bA7IPCWx.js.gz +0 -0
- package/dist/client/assets/linear-Cd102271.js.br +0 -0
- package/dist/client/assets/linear-Cd102271.js.gz +0 -0
- package/dist/client/assets/mermaid.core-C4wd6qUT.js.br +0 -0
- package/dist/client/assets/mermaid.core-C4wd6qUT.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DMxZDI3z.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DMxZDI3z.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-aU8HdPsL.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-aU8HdPsL.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BliPfrj_.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BliPfrj_.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-esWyI6AY.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-esWyI6AY.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CBoaEIHy.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CBoaEIHy.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-FB5bPmYm.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-FB5bPmYm.js.gz +0 -0
- package/dist/client/assets/rdpSession-DnBdxkm1.js.br +0 -0
- package/dist/client/assets/rdpSession-DnBdxkm1.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-DDmic9E5.js.br +0 -0
- package/dist/client/assets/referenceLongPress-DDmic9E5.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CpokwhJY.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CpokwhJY.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DXQDOMAh.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DXQDOMAh.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Dn3ZooAM.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Dn3ZooAM.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-PItE9Y1o.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-PItE9Y1o.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BBZ01SXU.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BBZ01SXU.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BHm-8VYK.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BY8IlbkR.js.br +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BuCvfzGX.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-DyY9g2Hx.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DyY9g2Hx.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-Dy0PQ4-U.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-Dy0PQ4-U.js.gz +0 -0
- package/dist/client/assets/videoCompression-BDNHhfKS.js.br +0 -0
- package/dist/client/assets/videoCompression-BDNHhfKS.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DtBEPD3N.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DtBEPD3N.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-B2K1XjDQ.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-B2K1XjDQ.js.gz +0 -0
|
Binary file
|
|
Binary file
|
package/dist/client/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
if(!self.define){let e,s={};const n=(n,i)=>(n=new URL(n+".js",i).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(i,a)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let r={};const c=e=>n(e,o),d={module:{uri:o},exports:r,require:c};s[o]=Promise.all(i.map(e=>d[e]||c(e))).then(e=>(a(...e),r))}}define(["./workbox-9845bf53"],function(e){"use strict";importScripts("sw-encrypted-background.js","sw-notifications.js","sw-secure-api.js","sw-downloads.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"index.html",revision:"
|
|
1
|
+
if(!self.define){let e,s={};const n=(n,i)=>(n=new URL(n+".js",i).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(i,a)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let r={};const c=e=>n(e,o),d={module:{uri:o},exports:r,require:c};s[o]=Promise.all(i.map(e=>d[e]||c(e))).then(e=>(a(...e),r))}}define(["./workbox-9845bf53"],function(e){"use strict";importScripts("sw-encrypted-background.js","sw-notifications.js","sw-secure-api.js","sw-downloads.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"index.html",revision:"3a82afb4a898c5c563748bbe9a5f4920"},{url:"assets/app-By1Q_guu.js",revision:null},{url:"assets/react-XDCjal2T.js",revision:null},{url:"assets/dnd-BCFAPC0L.js",revision:null},{url:"assets/terminal-C7X98Amf.js",revision:null},{url:"assets/index-A-ZC9mbr.css",revision:null},{url:"icons/agents/opencode.svg",revision:"851c2931d95821ba2b39b68587b48976"},{url:"icons/agents/kimi.svg",revision:"5366c710f4f763022fe159a69cf17409"},{url:"icons/agents/grok.svg",revision:"cd745b0b964545bc7a6b4f36021c3298"},{url:"icons/agents/goose.svg",revision:"c79b927278983c19098d77b4b0c11ab3"},{url:"icons/agents/gemini.svg",revision:"7a64f6d6f80a908255e9c0256903882e"},{url:"icons/agents/droid.svg",revision:"f28723b8c5c115e0908ba3c72613e256"},{url:"icons/agents/cursor.svg",revision:"a1528f3d3cda497f75a5db28defb19de"},{url:"icons/agents/copilot.svg",revision:"dd52e96c1eac0b6ec4bedcd0ddd3b52d"},{url:"icons/agents/codex.svg",revision:"608e55babb0b217e1f2da287342b46d3"},{url:"icons/agents/claude.svg",revision:"08af0225b7acdd8e8f573f3247e5e1c3"},{url:"icons/agents/amp.svg",revision:"af12f8b33faa81e4d46ea7b93c7e43f3"},{url:"apple-touch-icon-180x180.png",revision:"628c2e2f28a9b545aa57be1352025643"},{url:"boot-logo.png",revision:"2e3a25efd9b0b8ca1eab53b5f2284556"},{url:"favicon.ico",revision:"2994debfa6f59a0898e24eb0c54d64b5"},{url:"ipad-status-bar.js",revision:"3a8080edb8a5cc658a7908f6ad69537f"},{url:"maskable-icon-512x512.png",revision:"4d489331fd81f0713c400b26426442b9"},{url:"pwa-192x192.png",revision:"4d6f899888cfb8ccd7a1fd3c9e583ad2"},{url:"pwa-512x512.png",revision:"0885dd7ec8932e7f878b013d51a380a4"},{url:"robots.txt",revision:"d9339907142917dee4aac78494ac9cfe"},{url:"sw-downloads.js",revision:"48dab2e78bd93b4eee27fca487eccfde"},{url:"sw-notifications.js",revision:"fde578590ea8b0540d488ef448b48f45"},{url:"sw-secure-api.js",revision:"cf2bdbd03d41d66d60344c0b5a1075de"},{url:"manifest.webmanifest",revision:"83f9a43b74c236012adafb077fb6d774"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html"),{denylist:[/^\/api\//,/^\/__termdock-download\//,/^\/health$/]})),e.registerRoute(/\/assets\/.*\.(?:js|css)$/,new e.CacheFirst({cacheName:"termdock-lazy-assets-v1",plugins:[new e.CacheableResponsePlugin({statuses:[0,200]}),new e.ExpirationPlugin({maxEntries:96,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\/fonts\/terminal\/[^/]+-[a-f0-9]{12}\.woff2$/,new e.CacheFirst({cacheName:"termdock-fonts-v2",plugins:[new e.CacheableResponsePlugin({statuses:[0,200]}),new e.ExpirationPlugin({maxEntries:384,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\/fonts\/.*\.(?:woff2|ttf)$/,new e.StaleWhileRevalidate({cacheName:"termdock-fonts-v1",plugins:[new e.ExpirationPlugin({maxEntries:24,maxAgeSeconds:2592e3})]}),"GET")});
|
package/dist/client/sw.js.br
CHANGED
|
Binary file
|
package/dist/client/sw.js.gz
CHANGED
|
Binary file
|
package/dist/server/entry.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createPasswordLoginRouter } from './federation/passwordLoginRoutes.js';
|
|
|
11
11
|
import { apiAccessGate, isTrustedLocalRequest } from './utils/apiAccess.js';
|
|
12
12
|
import { isEncryptedRequest } from './federation/requestContext.js';
|
|
13
13
|
import { createFederationRuntime } from './federation/runtime.js';
|
|
14
|
+
import { startWebTransportServer } from './federation/webTransportServer.js';
|
|
14
15
|
import { RouteAccess } from './federation/routeAccess.js';
|
|
15
16
|
import { RouteInvitationStore } from './federation/routeInvitations.js';
|
|
16
17
|
import { attachRegisteredDirectTargets, verifyDirectTarget } from './federation/directRoutes.js';
|
|
@@ -470,6 +471,9 @@ export function startServer(options = {}) {
|
|
|
470
471
|
runtimeMonitor,
|
|
471
472
|
});
|
|
472
473
|
const { server, scheme } = createServerForApp(app, options);
|
|
474
|
+
let webTransport;
|
|
475
|
+
const webTransportDescriptor = () => webTransport?.descriptor();
|
|
476
|
+
server.once('close', () => webTransport?.close());
|
|
473
477
|
const collaborationChannels = new Map();
|
|
474
478
|
const reverseCollaboration = (id) => [...(collaborationChannels.get(id) ?? [])].find(rpc => !rpc.closed);
|
|
475
479
|
let collaborationService;
|
|
@@ -515,6 +519,7 @@ export function startServer(options = {}) {
|
|
|
515
519
|
const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
|
|
516
520
|
terminal: handleTerminalWebSocket, control: handleControlWebSocket, android: handleAndroidWebSocket, computer: handleComputerWebSocket,
|
|
517
521
|
}, {
|
|
522
|
+
webTransport: webTransportDescriptor,
|
|
518
523
|
resolveSessionId: resolveStableFederationSessionId,
|
|
519
524
|
collaborationConnected: (subjectId, rpc) => {
|
|
520
525
|
const channels = collaborationChannels.get(subjectId) ?? new Set();
|
|
@@ -727,29 +732,13 @@ export function startServer(options = {}) {
|
|
|
727
732
|
certWatcher.start();
|
|
728
733
|
let latestLocalAccessState = localAccessManager.getState();
|
|
729
734
|
let latestOnboardingUrl = null;
|
|
730
|
-
// WebSocket
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
// perMessageDeflate: 终端输出是纯文本,压缩比通常 5-10x。弱网/跨城(高 RTT、
|
|
735
|
-
// 低带宽)下,刷新页面时 N 个终端各自一次性回放全量 scrollback(每个可达
|
|
736
|
-
// 100KB),未压缩会同时挤满链路、肉眼可见地卡几秒。开启压缩后带宽需求直接
|
|
737
|
-
// 降到 1/5~1/10。参数:
|
|
738
|
-
// - threshold 1024:小于 1KB 的小包(按键回显等)不压缩,避免 CPU 浪费。
|
|
739
|
-
// - concurrencyLimit:限制并发压缩任务,防止突发回放打满 CPU。
|
|
740
|
-
// - zlib memLevel 7 + level 6:在压缩比和内存/CPU 间取均衡,避免每连接
|
|
741
|
-
// 分配过大 zlib 上下文(默认 memLevel 8 内存更高)。
|
|
742
|
-
// - serverNoContextTakeover:每条消息独立压缩上下文,降低长连接常驻内存。
|
|
735
|
+
// Both public WebSocket endpoints carry Noise ciphertext. Compress eligible
|
|
736
|
+
// terminal records before encryption instead of spending CPU deflating random
|
|
737
|
+
// ciphertext (including at relay hops).
|
|
743
738
|
const wss = new WebSocketServer({
|
|
744
739
|
noServer: true,
|
|
745
740
|
maxPayload: 1024 * 1024,
|
|
746
|
-
perMessageDeflate:
|
|
747
|
-
threshold: 1024,
|
|
748
|
-
concurrencyLimit: 10,
|
|
749
|
-
serverNoContextTakeover: true,
|
|
750
|
-
clientNoContextTakeover: true,
|
|
751
|
-
zlibDeflateOptions: { level: 6, memLevel: 7 },
|
|
752
|
-
},
|
|
741
|
+
perMessageDeflate: false,
|
|
753
742
|
});
|
|
754
743
|
server.on('upgrade', (request, socket, head) => {
|
|
755
744
|
if (wss.clients.size >= 256) {
|
|
@@ -815,6 +804,26 @@ export function startServer(options = {}) {
|
|
|
815
804
|
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
|
|
816
805
|
console.log(`Termdock server running at ${scheme}://${displayHost}:${listeningPort}`);
|
|
817
806
|
console.log(`Health check: ${scheme}://${displayHost}:${listeningPort}/health`);
|
|
807
|
+
if ((options.quic ?? process.env.TERMDOCK_QUIC !== '0') && (scheme === 'https' || process.env.TERMDOCK_PUBLIC_ORIGIN?.startsWith('https://'))) {
|
|
808
|
+
const quicPort = process.env.TERMDOCK_QUIC_PORT === undefined ? listeningPort : Number(process.env.TERMDOCK_QUIC_PORT);
|
|
809
|
+
void federation.then(async (runtime) => {
|
|
810
|
+
if (!Number.isSafeInteger(quicPort) || quicPort < 1 || quicPort > 65535)
|
|
811
|
+
throw new Error('Invalid TERMDOCK_QUIC_PORT');
|
|
812
|
+
const transport = await startWebTransportServer({
|
|
813
|
+
directory: path.join(homedir(), '.termdock', 'federation'), host: listeningHost, port: quicPort,
|
|
814
|
+
hostAllowed: isAllowedHost,
|
|
815
|
+
originAllowed: (origin, authority) => secureChannelOriginAllowed(origin, () => isUpgradeOriginAllowed(origin, authority)),
|
|
816
|
+
openAccessAllowed: (origin, authority) => !!origin && isUpgradeOriginAllowed(origin, authority),
|
|
817
|
+
accept: (duplex, context) => runtime.acceptDuplex(duplex, context),
|
|
818
|
+
});
|
|
819
|
+
if (!server.listening) {
|
|
820
|
+
transport.close();
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
webTransport = transport;
|
|
824
|
+
console.log(`QUIC/WebTransport listening on UDP ${quicPort}; independent encrypted streams enabled`);
|
|
825
|
+
}).catch(error => console.warn('[quic] Encrypted WebSocket remains available:', error instanceof Error ? error.message : 'Transport unavailable'));
|
|
826
|
+
}
|
|
818
827
|
const onboardingServerState = scheme === 'https'
|
|
819
828
|
? startOnboardingServer({ httpsPort: listeningPort, caCertPath: options.httpsCaPath })
|
|
820
829
|
: { server: null, url: null };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TRANSFER_CHUNK_BYTES } from './transferWindow.js';
|
|
2
|
+
export const BINARY_RECORD_FLAG = 0x80000000;
|
|
3
|
+
const encoder = new TextEncoder();
|
|
4
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
5
|
+
/** Negotiated file chunks avoid JSON/base64 expansion; the entire record,
|
|
6
|
+
* including its stream id and kind, is still encrypted by Noise. */
|
|
7
|
+
export function binaryPacketBody(packet) {
|
|
8
|
+
if (!(packet.bytes instanceof Uint8Array))
|
|
9
|
+
return;
|
|
10
|
+
if (!['upload', 'chunk'].includes(packet.type))
|
|
11
|
+
throw new Error('Invalid binary record type');
|
|
12
|
+
const id = encoder.encode(packet.id);
|
|
13
|
+
if (!id.length || id.length > 128 || packet.bytes.length > TRANSFER_CHUNK_BYTES)
|
|
14
|
+
throw new Error('Invalid binary record size');
|
|
15
|
+
const result = new Uint8Array(2 + id.length + packet.bytes.length);
|
|
16
|
+
result[0] = packet.type === 'upload' ? 1 : 2;
|
|
17
|
+
result[1] = id.length;
|
|
18
|
+
result.set(id, 2);
|
|
19
|
+
result.set(packet.bytes, 2 + id.length);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
export function readBinaryPacket(body) {
|
|
23
|
+
const kind = body[0], idLength = body[1];
|
|
24
|
+
if (![1, 2].includes(kind) || !idLength || idLength > 128 || body.length < 2 + idLength
|
|
25
|
+
|| body.length - 2 - idLength > TRANSFER_CHUNK_BYTES)
|
|
26
|
+
throw new Error('Invalid binary record');
|
|
27
|
+
return { type: kind === 1 ? 'upload' : 'chunk', id: decoder.decode(body.subarray(2, 2 + idLength)), bytes: body.slice(2 + idLength) };
|
|
28
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { compressRecord } from './recordCompression.js';
|
|
2
|
+
import { BINARY_RECORD_FLAG, binaryPacketBody, readBinaryPacket } from './binaryPacket.js';
|
|
1
3
|
/** Bounded queues are also used at the untrusted, pre-handshake boundary. */
|
|
2
4
|
export class AsyncQueue {
|
|
3
5
|
limit;
|
|
@@ -62,6 +64,7 @@ export class PacketChannel {
|
|
|
62
64
|
outgoing = new AsyncQueue(256);
|
|
63
65
|
outgoingBytes = 0;
|
|
64
66
|
queuedBytesById = new Map();
|
|
67
|
+
writeWaiters = new Set();
|
|
65
68
|
closed = false;
|
|
66
69
|
get hasPendingWrites() { return this.outgoingBytes > 0; }
|
|
67
70
|
queuedBytesFor(id) { return this.queuedBytesById.get(id) ?? 0; }
|
|
@@ -72,7 +75,7 @@ export class PacketChannel {
|
|
|
72
75
|
this.done = duplex.sink((async function* () {
|
|
73
76
|
for await (const packet of self.outgoing) {
|
|
74
77
|
try {
|
|
75
|
-
yield packet.bytes;
|
|
78
|
+
yield packet.compress ? await compressRecord(packet.bytes, packet.id) : packet.bytes;
|
|
76
79
|
}
|
|
77
80
|
finally {
|
|
78
81
|
self.releaseQueuedPacket(packet);
|
|
@@ -81,27 +84,66 @@ export class PacketChannel {
|
|
|
81
84
|
})());
|
|
82
85
|
void this.done.then(() => this.discardPending(), error => this.discardPending(error instanceof Error ? error : new Error('Transport failed')));
|
|
83
86
|
}
|
|
84
|
-
send(packet) {
|
|
87
|
+
send(packet, compress = false) {
|
|
88
|
+
if (this.closed)
|
|
89
|
+
throw new Error('Channel closed');
|
|
85
90
|
// The item-count cap protects against floods of tiny records. Check it
|
|
86
91
|
// before encoding and keep this queue alive when it is temporarily full.
|
|
87
92
|
if (this.outgoing.size >= 256)
|
|
88
93
|
throw new PacketQueueBackpressureError('Record queue item limit exceeded');
|
|
89
|
-
const
|
|
94
|
+
const binary = binaryPacketBody(packet);
|
|
95
|
+
const body = binary ?? encoder.encode(JSON.stringify(packet));
|
|
90
96
|
if (body.length > MAX_PACKET)
|
|
91
97
|
throw new Error('Record too large');
|
|
92
98
|
const bytes = new Uint8Array(body.length + 4);
|
|
93
|
-
new DataView(bytes.buffer).setUint32(0, body.length);
|
|
99
|
+
new DataView(bytes.buffer).setUint32(0, binary ? body.length + BINARY_RECORD_FLAG : body.length);
|
|
94
100
|
bytes.set(body, 4);
|
|
95
101
|
if (this.outgoingBytes + bytes.byteLength > 4 * 1024 * 1024)
|
|
96
102
|
throw new PacketQueueBackpressureError('Record queue byte budget exceeded');
|
|
97
|
-
const queued = { id: packet.id, bytes };
|
|
103
|
+
const queued = { id: packet.id, bytes, compress };
|
|
98
104
|
if (!this.outgoing.tryPush(queued))
|
|
99
105
|
throw new PacketQueueBackpressureError('Record queue item limit exceeded');
|
|
100
106
|
this.outgoingBytes += bytes.byteLength;
|
|
101
107
|
this.queuedBytesById.set(packet.id, this.queuedBytesFor(packet.id) + bytes.byteLength);
|
|
102
108
|
}
|
|
109
|
+
/** Bulk producers wait below the hard cap, leaving room for input, ACKs and
|
|
110
|
+
* cancellation. A busy file stream must not tear down the shared connection. */
|
|
111
|
+
async sendBulk(packet, signal, beforeSend) {
|
|
112
|
+
const started = Date.now();
|
|
113
|
+
while (true) {
|
|
114
|
+
if (signal.aborted)
|
|
115
|
+
throw signal.reason ?? new Error('CANCELLED');
|
|
116
|
+
if (this.closed)
|
|
117
|
+
throw new Error('Channel closed');
|
|
118
|
+
if (this.outgoingBytes < 512 * 1024 && this.outgoing.size < 128) {
|
|
119
|
+
beforeSend?.();
|
|
120
|
+
try {
|
|
121
|
+
this.send(packet);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (!(error instanceof PacketQueueBackpressureError))
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (Date.now() - started >= 30_000)
|
|
130
|
+
throw new Error('Encrypted transport congested');
|
|
131
|
+
await new Promise((resolve, reject) => {
|
|
132
|
+
const cleanup = () => { clearTimeout(timer); signal.removeEventListener('abort', abort); this.writeWaiters.delete(wake); };
|
|
133
|
+
const abort = () => { cleanup(); reject(signal.reason ?? new Error('CANCELLED')); };
|
|
134
|
+
const wake = () => { cleanup(); resolve(); };
|
|
135
|
+
const timer = setTimeout(() => { cleanup(); reject(new Error('Encrypted transport congested')); }, Math.max(1, 30_000 - (Date.now() - started)));
|
|
136
|
+
this.writeWaiters.add(wake);
|
|
137
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
138
|
+
if (signal.aborted)
|
|
139
|
+
abort();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
103
143
|
releaseQueuedPacket(packet) {
|
|
104
144
|
this.outgoingBytes = Math.max(0, this.outgoingBytes - packet.bytes.byteLength);
|
|
145
|
+
for (const wake of this.writeWaiters)
|
|
146
|
+
wake();
|
|
105
147
|
const current = this.queuedBytesById.get(packet.id);
|
|
106
148
|
if (current === undefined)
|
|
107
149
|
return;
|
|
@@ -127,6 +169,8 @@ export class PacketChannel {
|
|
|
127
169
|
this.outgoing.end(error);
|
|
128
170
|
this.outgoingBytes = 0;
|
|
129
171
|
this.queuedBytesById.clear();
|
|
172
|
+
for (const wake of this.writeWaiters)
|
|
173
|
+
wake();
|
|
130
174
|
this.duplex.close?.(error);
|
|
131
175
|
}
|
|
132
176
|
async *read() {
|
|
@@ -135,6 +179,8 @@ export class PacketChannel {
|
|
|
135
179
|
let bufferedBytes = 0;
|
|
136
180
|
let spareBlock;
|
|
137
181
|
const parseFrame = (frame) => {
|
|
182
|
+
if (frame[0] & 0x80)
|
|
183
|
+
return readBinaryPacket(frame.subarray(4));
|
|
138
184
|
const packet = JSON.parse(decoder.decode(frame.subarray(4)));
|
|
139
185
|
if (!packet || typeof packet !== 'object' || typeof packet.id !== 'string' || typeof packet.type !== 'string')
|
|
140
186
|
throw new Error('Invalid record');
|
|
@@ -232,7 +278,7 @@ export class PacketChannel {
|
|
|
232
278
|
if (bufferedBytes + chunk.byteLength > MAX_BUFFERED_PACKET_BYTES)
|
|
233
279
|
throw new Error('Record buffer exceeded');
|
|
234
280
|
if (bufferedBytes === 0 && chunk.byteLength >= 5) {
|
|
235
|
-
const size = ((chunk[0] * 0x1000000) + (chunk[1] << 16) + (chunk[2] << 8) + chunk[3]) >>> 0;
|
|
281
|
+
const size = (((chunk[0] & 0x7f) * 0x1000000) + (chunk[1] << 16) + (chunk[2] << 8) + chunk[3]) >>> 0;
|
|
236
282
|
if (size > MAX_PACKET || size === 0)
|
|
237
283
|
throw new Error('Invalid record size');
|
|
238
284
|
if (chunk.byteLength === size + 4) {
|
|
@@ -243,7 +289,7 @@ export class PacketChannel {
|
|
|
243
289
|
if (chunk.byteLength)
|
|
244
290
|
append(chunk);
|
|
245
291
|
while (bufferedBytes >= 4) {
|
|
246
|
-
const size = ((byteAt(0) * 0x1000000) + (byteAt(1) << 16) + (byteAt(2) << 8) + byteAt(3)) >>> 0;
|
|
292
|
+
const size = (((byteAt(0) & 0x7f) * 0x1000000) + (byteAt(1) << 16) + (byteAt(2) << 8) + byteAt(3)) >>> 0;
|
|
247
293
|
if (size > MAX_PACKET || size === 0)
|
|
248
294
|
throw new Error('Invalid record size');
|
|
249
295
|
if (bufferedBytes < size + 4)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const MAX_RECORD_BYTES = 2 * 1024 * 1024;
|
|
2
|
+
const encoder = new TextEncoder();
|
|
3
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
4
|
+
async function transform(bytes, stream) {
|
|
5
|
+
const source = new Blob([bytes]).stream();
|
|
6
|
+
const reader = source.pipeThrough(stream).getReader();
|
|
7
|
+
const chunks = [];
|
|
8
|
+
let size = 0;
|
|
9
|
+
try {
|
|
10
|
+
while (true) {
|
|
11
|
+
const { done, value } = await reader.read();
|
|
12
|
+
if (done)
|
|
13
|
+
break;
|
|
14
|
+
size += value.byteLength;
|
|
15
|
+
if (size > MAX_RECORD_BYTES)
|
|
16
|
+
throw new Error('Expanded record exceeds budget');
|
|
17
|
+
chunks.push(value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
await reader.cancel(error).catch(() => { });
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
reader.releaseLock();
|
|
26
|
+
}
|
|
27
|
+
const result = new Uint8Array(size);
|
|
28
|
+
let offset = 0;
|
|
29
|
+
for (const chunk of chunks) {
|
|
30
|
+
result.set(chunk, offset);
|
|
31
|
+
offset += chunk.byteLength;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/** Independent records avoid retaining compression dictionaries across messages.
|
|
36
|
+
* Only explicitly negotiated terminal output is eligible, before Noise encryption. */
|
|
37
|
+
export async function compressRecord(frame, id) {
|
|
38
|
+
if (frame.byteLength < 4096 || typeof CompressionStream === 'undefined')
|
|
39
|
+
return frame;
|
|
40
|
+
const compressed = await transform(frame.subarray(4), new CompressionStream('gzip'));
|
|
41
|
+
let binary = '';
|
|
42
|
+
for (let offset = 0; offset < compressed.length; offset += 8192)
|
|
43
|
+
binary += String.fromCharCode(...compressed.subarray(offset, offset + 8192));
|
|
44
|
+
const body = encoder.encode(JSON.stringify({ type: 'compressed', id, encoding: 'gzip', data: btoa(binary) }));
|
|
45
|
+
// Account for base64 and the envelope: already compressed content goes as-is.
|
|
46
|
+
if (body.byteLength + 4 >= frame.byteLength * 0.9)
|
|
47
|
+
return frame;
|
|
48
|
+
const result = new Uint8Array(body.byteLength + 4);
|
|
49
|
+
new DataView(result.buffer).setUint32(0, body.byteLength);
|
|
50
|
+
result.set(body, 4);
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
export async function expandRecord(data) {
|
|
54
|
+
if (typeof data !== 'string' || data.length > Math.ceil(MAX_RECORD_BYTES / 3) * 4)
|
|
55
|
+
throw new Error('Invalid compressed record');
|
|
56
|
+
const bytes = Uint8Array.from(atob(data), c => c.charCodeAt(0));
|
|
57
|
+
const expanded = await transform(bytes, new DecompressionStream('gzip'));
|
|
58
|
+
return JSON.parse(decoder.decode(expanded));
|
|
59
|
+
}
|
|
@@ -18,19 +18,22 @@ import { readTerminalHandshakeDimensions } from '../utils/terminalHandshakeDimen
|
|
|
18
18
|
import { openAccessAllowed } from './openAccess.js';
|
|
19
19
|
import { isAuthEnabled, getPasswordCredentialFingerprint, getPasswordVerifier, getLoginBlockMs, recordLoginFailure, recordLoginSuccess } from '../utils/authProtection.js';
|
|
20
20
|
import { MAX_OPEN_SECURE_SOCKETS, MAX_SERVER_HTTP_REQUESTS } from './streamLimits.js';
|
|
21
|
+
import { MAX_TRANSFER_WINDOW, TRANSFER_CHUNK_BYTES, TransferWindow, transferWindow } from './transferWindow.js';
|
|
21
22
|
class LogicalSocket extends EventEmitter {
|
|
22
23
|
channel;
|
|
23
24
|
id;
|
|
24
25
|
canRead;
|
|
26
|
+
compress;
|
|
25
27
|
OPEN = 1;
|
|
26
28
|
readyState = 1;
|
|
27
29
|
get bufferedAmount() { return this.channel.queuedBytesFor(this.id); }
|
|
28
30
|
maxQueuedBytes;
|
|
29
|
-
constructor(channel, id, canRead, maxQueuedBytes = 4 * 1024 * 1024) {
|
|
31
|
+
constructor(channel, id, canRead, maxQueuedBytes = 4 * 1024 * 1024, compress = false) {
|
|
30
32
|
super();
|
|
31
33
|
this.channel = channel;
|
|
32
34
|
this.id = id;
|
|
33
35
|
this.canRead = canRead;
|
|
36
|
+
this.compress = compress;
|
|
34
37
|
this.maxQueuedBytes = maxQueuedBytes;
|
|
35
38
|
}
|
|
36
39
|
send(data) {
|
|
@@ -48,7 +51,7 @@ class LogicalSocket extends EventEmitter {
|
|
|
48
51
|
return;
|
|
49
52
|
}
|
|
50
53
|
try {
|
|
51
|
-
this.channel.send({ type: 'ws-data', id: this.id, data });
|
|
54
|
+
this.channel.send({ type: 'ws-data', id: this.id, data }, this.compress);
|
|
52
55
|
}
|
|
53
56
|
catch {
|
|
54
57
|
this.close(1013, 'Output consumer is too slow');
|
|
@@ -113,6 +116,11 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
113
116
|
markEncryptedRequest(request, typeof subject === 'string' ? subject : undefined, fullService);
|
|
114
117
|
app(request, response);
|
|
115
118
|
});
|
|
119
|
+
// The outer encrypted consumer can apply backpressure long after a local
|
|
120
|
+
// response was written. Expiring this private keepalive socket while Undici
|
|
121
|
+
// still drains it can truncate a slow download (UND_ERR_SOCKET / terminated).
|
|
122
|
+
// Let the local client expire idle connections after it finishes consuming.
|
|
123
|
+
internal.keepAliveTimeout = 0;
|
|
116
124
|
await new Promise((resolve, reject) => { internal.once('error', reject); internal.listen(0, '127.0.0.1', resolve); });
|
|
117
125
|
const address = internal.address();
|
|
118
126
|
if (!address || typeof address === 'string')
|
|
@@ -132,7 +140,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
132
140
|
const inventory = await response.json();
|
|
133
141
|
return Array.isArray(inventory.clientSessions) ? inventory.clientSessions : [];
|
|
134
142
|
}
|
|
135
|
-
async function
|
|
143
|
+
async function acceptDuplex(duplex, context = {}) {
|
|
136
144
|
let collaborationRpc;
|
|
137
145
|
let unregisterCollaboration;
|
|
138
146
|
const open = (action) => openAccessAllowed(context.allowOpenAccess === true, isAuthEnabled(), action);
|
|
@@ -161,7 +169,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
161
169
|
const sockets = new Map();
|
|
162
170
|
let revokeTimer;
|
|
163
171
|
try {
|
|
164
|
-
const secured = await secureConnection({ identity, duplex
|
|
172
|
+
const secured = await secureConnection({ identity, duplex, initiator: false, signal: AbortSignal.timeout(15000) });
|
|
165
173
|
const subjectId = secured.authenticatedPeerId;
|
|
166
174
|
channel = new PacketChannel(secured.duplex);
|
|
167
175
|
channels.add(channel);
|
|
@@ -225,28 +233,16 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
225
233
|
const { done, value } = await reader.read();
|
|
226
234
|
if (done)
|
|
227
235
|
break;
|
|
228
|
-
for (let offset = 0; offset < value.length; offset +=
|
|
236
|
+
for (let offset = 0; offset < value.length; offset += TRANSFER_CHUNK_BYTES) {
|
|
229
237
|
httpAllowed(operation.head);
|
|
230
|
-
await
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
operation.ack = () => { cleanup(); resolve(); };
|
|
236
|
-
if (operation.abort.signal.aborted) {
|
|
237
|
-
abort();
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
try {
|
|
241
|
-
sendHttp({ type: 'chunk', id, data: toBase64(value.subarray(offset, offset + 65536)) });
|
|
242
|
-
}
|
|
243
|
-
catch (error) {
|
|
244
|
-
cleanup();
|
|
245
|
-
reject(error);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
238
|
+
await operation.responseWindow.wait();
|
|
239
|
+
httpAllowed(operation.head);
|
|
240
|
+
operation.responseWindow.sent();
|
|
241
|
+
const chunk = value.subarray(offset, offset + TRANSFER_CHUNK_BYTES);
|
|
242
|
+
await channel.sendBulk({ type: 'chunk', id, ...(operation.head.binaryChunks === true ? { bytes: chunk } : { data: toBase64(chunk) }) }, operation.abort.signal, () => httpAllowed(operation.head));
|
|
248
243
|
}
|
|
249
244
|
}
|
|
245
|
+
await operation.responseWindow.wait(true);
|
|
250
246
|
sendHttp({ type: 'end', id });
|
|
251
247
|
}
|
|
252
248
|
catch (error) {
|
|
@@ -393,7 +389,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
393
389
|
}
|
|
394
390
|
else if (packet.type === 'permissions') {
|
|
395
391
|
const grants = store.listEffective({ subjectId, serviceId });
|
|
396
|
-
send({ type: 'result', id: packet.id, subjectId, serviceId, fullService: full(subjectId), grants });
|
|
392
|
+
send({ type: 'result', id: packet.id, subjectId, serviceId, fullService: full(subjectId), grants, httpWindow: MAX_TRANSFER_WINDOW, binaryChunks: true, webTransport: options.webTransport?.() });
|
|
397
393
|
}
|
|
398
394
|
else if (packet.type === 'session-list') {
|
|
399
395
|
const inventory = await sessionInventory();
|
|
@@ -515,13 +511,15 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
515
511
|
send({ type: 'result', id: packet.id, grant });
|
|
516
512
|
}
|
|
517
513
|
else if (packet.type === 'http') {
|
|
518
|
-
if (operations.size >= MAX_SERVER_HTTP_REQUESTS || operations.has(packet.id) || sockets.has(packet.id))
|
|
514
|
+
if ((context.singleOperation ? operations.size + sockets.size >= 1 : operations.size >= MAX_SERVER_HTTP_REQUESTS) || operations.has(packet.id) || sockets.has(packet.id))
|
|
519
515
|
throw new Error('STREAM_LIMIT');
|
|
520
516
|
httpAllowed(packet);
|
|
521
517
|
const upload = !['GET', 'HEAD'].includes(String(packet.method));
|
|
522
518
|
if (upload && uploadSlots >= 8)
|
|
523
519
|
throw new Error('UPLOAD_CONCURRENCY_LIMIT');
|
|
524
|
-
const
|
|
520
|
+
const window = transferWindow(packet.window);
|
|
521
|
+
const abort = new AbortController();
|
|
522
|
+
const operation = { head: packet, body: new AsyncQueue(Math.max(2, window)), size: 0, abort, state: 'uploading', updatedAt: Date.now(), uploadSlot: upload, responseWindow: new TransferWindow(window, abort.signal) };
|
|
525
523
|
if (upload)
|
|
526
524
|
uploadSlots++;
|
|
527
525
|
operations.set(packet.id, operation);
|
|
@@ -530,13 +528,14 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
530
528
|
}
|
|
531
529
|
else if (packet.type === 'upload') {
|
|
532
530
|
const operation = operations.get(packet.id);
|
|
533
|
-
if (!operation || operation.state !== 'uploading'
|
|
531
|
+
if (!operation || operation.state !== 'uploading'
|
|
532
|
+
|| !(operation.head.binaryChunks === true && packet.bytes instanceof Uint8Array) && typeof packet.data !== 'string')
|
|
534
533
|
throw new Error('INVALID_UPLOAD');
|
|
535
534
|
httpAllowed(operation.head);
|
|
536
|
-
const bytes = fromBase64(packet.data);
|
|
535
|
+
const bytes = operation.head.binaryChunks === true && packet.bytes instanceof Uint8Array ? packet.bytes : fromBase64(packet.data);
|
|
537
536
|
operation.size += bytes.length;
|
|
538
537
|
const limit = new URL(String(operation.head.path), 'http://inner').pathname === '/api/terminal/fs/upload' ? 110 * 1024 * 1024 : 5 * 1024 * 1024;
|
|
539
|
-
if (['GET', 'HEAD'].includes(String(operation.head.method)) || bytes.length >
|
|
538
|
+
if (['GET', 'HEAD'].includes(String(operation.head.method)) || bytes.length > TRANSFER_CHUNK_BYTES || operation.size > limit) {
|
|
540
539
|
cancelHttp(operation);
|
|
541
540
|
operations.delete(packet.id);
|
|
542
541
|
throw new Error('UPLOAD_LIMIT');
|
|
@@ -561,10 +560,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
561
560
|
void runHttp(packet.id, operation);
|
|
562
561
|
}
|
|
563
562
|
else if (packet.type === 'ack') {
|
|
564
|
-
|
|
565
|
-
operation?.ack?.();
|
|
566
|
-
if (operation)
|
|
567
|
-
operation.ack = undefined;
|
|
563
|
+
operations.get(packet.id)?.responseWindow.acknowledge();
|
|
568
564
|
}
|
|
569
565
|
else if (packet.type === 'cancel') {
|
|
570
566
|
const operation = operations.get(packet.id);
|
|
@@ -573,7 +569,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
573
569
|
operations.delete(packet.id);
|
|
574
570
|
}
|
|
575
571
|
else if (packet.type === 'ws-open') {
|
|
576
|
-
if (sockets.size >= MAX_OPEN_SECURE_SOCKETS || operations.has(packet.id) || sockets.has(packet.id))
|
|
572
|
+
if ((context.singleOperation ? operations.size + sockets.size >= 1 : sockets.size >= MAX_OPEN_SECURE_SOCKETS) || operations.has(packet.id) || sockets.has(packet.id))
|
|
577
573
|
throw new Error('STREAM_LIMIT');
|
|
578
574
|
if (typeof packet.path !== 'string' || !isBusinessApiPath('GET', packet.path))
|
|
579
575
|
throw new Error('API_NOT_ALLOWED');
|
|
@@ -591,7 +587,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
591
587
|
}
|
|
592
588
|
else
|
|
593
589
|
throw new Error('API_NOT_ALLOWED');
|
|
594
|
-
const logical = new LogicalSocket(channel, packet.id, () => (androidMatch || computer) ? full(subjectId) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]), match ? 1024 * 1024 : undefined);
|
|
590
|
+
const logical = new LogicalSocket(channel, packet.id, () => (androidMatch || computer) ? full(subjectId) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]), match ? 1024 * 1024 : undefined, !!match && packet.compression === 'gzip');
|
|
595
591
|
sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: Boolean(androidMatch), computer,
|
|
596
592
|
computerProtocol: computer ? url.searchParams.get('protocol') ?? 'vnc' : undefined });
|
|
597
593
|
logical.once('close', () => sockets.delete(packet.id));
|
|
@@ -665,7 +661,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
665
661
|
}
|
|
666
662
|
}
|
|
667
663
|
catch {
|
|
668
|
-
|
|
664
|
+
duplex.close?.(new Error('Secure channel closed'));
|
|
669
665
|
}
|
|
670
666
|
finally {
|
|
671
667
|
unregisterCollaboration?.();
|
|
@@ -687,6 +683,8 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
687
683
|
}
|
|
688
684
|
}
|
|
689
685
|
}
|
|
690
|
-
return { serviceId, identity, store,
|
|
686
|
+
return { serviceId, identity, store, acceptDuplex,
|
|
687
|
+
accept(socket, context = {}) { return acceptDuplex(socketDuplex(socket), context); },
|
|
688
|
+
close() { for (const channel of channels)
|
|
691
689
|
channel.close(); internal.close(); } };
|
|
692
690
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** One reliable QUIC stream carries one independently authenticated Noise
|
|
2
|
+
* channel. Closing it never closes unrelated streams in the QUIC session. */
|
|
3
|
+
export function streamDuplex(stream) {
|
|
4
|
+
const reader = stream.readable.getReader();
|
|
5
|
+
const writer = stream.writable.getWriter();
|
|
6
|
+
let closed = false;
|
|
7
|
+
let writingBytes = 0;
|
|
8
|
+
const close = (error) => {
|
|
9
|
+
if (closed)
|
|
10
|
+
return;
|
|
11
|
+
closed = true;
|
|
12
|
+
void reader.cancel(error).catch(() => { });
|
|
13
|
+
void (error ? writer.abort(error) : writer.close()).catch(() => { });
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
close,
|
|
17
|
+
bufferedBytes: () => writingBytes,
|
|
18
|
+
source: {
|
|
19
|
+
async *[Symbol.asyncIterator]() {
|
|
20
|
+
try {
|
|
21
|
+
while (!closed) {
|
|
22
|
+
const { done, value } = await reader.read();
|
|
23
|
+
if (done)
|
|
24
|
+
return;
|
|
25
|
+
if (!(value instanceof Uint8Array))
|
|
26
|
+
throw new Error('Invalid transport stream bytes');
|
|
27
|
+
yield value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
reader.releaseLock();
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
async sink(source) {
|
|
36
|
+
try {
|
|
37
|
+
for await (const chunk of source) {
|
|
38
|
+
for (let offset = 0; offset < chunk.byteLength; offset += 64 * 1024) {
|
|
39
|
+
if (closed)
|
|
40
|
+
throw new Error('Transport stream closed');
|
|
41
|
+
const bytes = chunk.subarray(offset, offset + 64 * 1024);
|
|
42
|
+
writingBytes = bytes.byteLength;
|
|
43
|
+
try {
|
|
44
|
+
await writer.write(bytes);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
writingBytes = 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await writer.close();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
close(error instanceof Error ? error : new Error('Transport stream failed'));
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
writer.releaseLock();
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Application flow control inside Noise. Old peers retain one chunk in flight. */
|
|
2
|
+
export const TRANSFER_CHUNK_BYTES = 64 * 1024;
|
|
3
|
+
export const MAX_TRANSFER_WINDOW = 8;
|
|
4
|
+
export function transferWindow(value) {
|
|
5
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 1
|
|
6
|
+
? Math.min(value, MAX_TRANSFER_WINDOW) : 1;
|
|
7
|
+
}
|
|
8
|
+
/** One producer and ordered acknowledgements; unsolicited ACKs grant no credit. */
|
|
9
|
+
export class TransferWindow {
|
|
10
|
+
limit;
|
|
11
|
+
signal;
|
|
12
|
+
outstanding = 0;
|
|
13
|
+
wake;
|
|
14
|
+
constructor(limit, signal) {
|
|
15
|
+
this.limit = limit;
|
|
16
|
+
this.signal = signal;
|
|
17
|
+
}
|
|
18
|
+
sent() {
|
|
19
|
+
if (this.outstanding >= this.limit)
|
|
20
|
+
throw new Error('Transfer window exceeded');
|
|
21
|
+
this.outstanding++;
|
|
22
|
+
}
|
|
23
|
+
acknowledge() {
|
|
24
|
+
if (this.outstanding === 0)
|
|
25
|
+
return;
|
|
26
|
+
this.outstanding--;
|
|
27
|
+
this.wake?.();
|
|
28
|
+
}
|
|
29
|
+
async wait(drained = false) {
|
|
30
|
+
while (true) {
|
|
31
|
+
if (this.signal.aborted)
|
|
32
|
+
throw this.signal.reason ?? new Error('CANCELLED');
|
|
33
|
+
if (this.outstanding < (drained ? 1 : this.limit))
|
|
34
|
+
return;
|
|
35
|
+
await new Promise((resolve, reject) => {
|
|
36
|
+
const cleanup = () => {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
this.signal.removeEventListener('abort', abort);
|
|
39
|
+
this.wake = undefined;
|
|
40
|
+
};
|
|
41
|
+
const abort = () => { cleanup(); reject(this.signal.reason ?? new Error('CANCELLED')); };
|
|
42
|
+
const timer = setTimeout(() => { cleanup(); reject(new Error('CONSUMER_TIMEOUT')); }, 30_000);
|
|
43
|
+
this.wake = () => { cleanup(); resolve(); };
|
|
44
|
+
this.signal.addEventListener('abort', abort, { once: true });
|
|
45
|
+
if (this.signal.aborted)
|
|
46
|
+
abort();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|