termdock 1.4.322 → 1.4.324
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/client/assets/App-7IUcmCLC.js +186 -0
- package/dist/client/assets/App-7IUcmCLC.js.br +0 -0
- package/dist/client/assets/App-7IUcmCLC.js.gz +0 -0
- package/dist/client/assets/{ArchitecturePanel-VDM6hrwp.js → ArchitecturePanel-CUAB0wFe.js} +6 -6
- package/dist/client/assets/ArchitecturePanel-CUAB0wFe.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-CUAB0wFe.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-B64j3i8o.js → ChangeWalkthroughPanel-CKIG8diY.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-CKIG8diY.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-CKIG8diY.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CIq-JAbF.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-CIq-JAbF.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CIq-JAbF.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-7vWDzH-d.js +2 -0
- package/dist/client/assets/{ComputerControlView-Co-TTYas.js → ComputerControlView-CPSI-M86.js} +2 -2
- package/dist/client/assets/ComputerControlView-CPSI-M86.js.br +0 -0
- package/dist/client/assets/ComputerControlView-CPSI-M86.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-kJczdJ6J.js → DagPlayground-BxocNRPm.js} +1 -1
- package/dist/client/assets/DagPlayground-BxocNRPm.js.br +0 -0
- package/dist/client/assets/DagPlayground-BxocNRPm.js.gz +0 -0
- package/dist/client/assets/{DiffLab-BaaioC-q.js → DiffLab-COGOFO0s.js} +2 -2
- package/dist/client/assets/DiffLab-COGOFO0s.js.br +0 -0
- package/dist/client/assets/DiffLab-COGOFO0s.js.gz +0 -0
- package/dist/client/assets/{DiffReview-Wfck3RIS.js → DiffReview-5ERdwz1C.js} +1 -1
- package/dist/client/assets/DiffReview-5ERdwz1C.js.br +0 -0
- package/dist/client/assets/DiffReview-5ERdwz1C.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-Dblr3wgw.js → DiffReviewLab-BSz9IND6.js} +1 -1
- package/dist/client/assets/DiffReviewLab-BSz9IND6.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-BSz9IND6.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-C-utC5Ga.js → DiffViewer-RT-p-ZBo.js} +8 -8
- package/dist/client/assets/DiffViewer-RT-p-ZBo.js.br +0 -0
- package/dist/client/assets/DiffViewer-RT-p-ZBo.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-BBLnHWVQ.js → ModelPreview-DI9anVWA.js} +1 -1
- package/dist/client/assets/ModelPreview-DI9anVWA.js.br +0 -0
- package/dist/client/assets/ModelPreview-DI9anVWA.js.gz +0 -0
- package/dist/client/assets/{RightSidebar-CUHSFkmZ.js → RightSidebar-DOLn8R7c.js} +36 -36
- package/dist/client/assets/RightSidebar-DOLn8R7c.js.br +0 -0
- package/dist/client/assets/RightSidebar-DOLn8R7c.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-DDd99QpA.js → abnfDiagram-VCTEODGH-CXd0unH5.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-CXd0unH5.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CXd0unH5.js.gz +0 -0
- package/dist/client/assets/app-DCFOZi_L.js +35 -0
- package/dist/client/assets/app-DCFOZi_L.js.br +0 -0
- package/dist/client/assets/app-DCFOZi_L.js.gz +0 -0
- package/dist/client/assets/{arc-C3dck_E0.js → arc-DCqJmX8M.js} +1 -1
- package/dist/client/assets/arc-DCqJmX8M.js.br +0 -0
- package/dist/client/assets/arc-DCqJmX8M.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-C3bCQc2w.js → architectureDiagram-5GKGNRK7-DGu_m54H.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DGu_m54H.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DGu_m54H.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-znMn6iCQ.js → blockDiagram-I7D4REHJ-BI0gsxDN.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-BI0gsxDN.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BI0gsxDN.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-C0sopIsj.js → c4Diagram-7LVT6UL2-Ct3xzGtD.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-Ct3xzGtD.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-Ct3xzGtD.js.gz +0 -0
- package/dist/client/assets/channel-BLSCUksA.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-f7USyccJ.js → chunk-2Q5K7J3B-BPd2UShA.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Ck2xirDs.js → chunk-5VM5RSS4-CoFZ5Fo3.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DaDCIj30.js → chunk-F27PBJKO-C5gZrGp4.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-C5gZrGp4.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-C5gZrGp4.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-DEC_On5x.js → chunk-IMKFNOWR-GW8b9ya-.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-GW8b9ya-.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-GW8b9ya-.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-BrswC3n2.js → chunk-JWPE2WC7-GpMoViQf.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-ClMbDV47.js → chunk-POPQ4Y6H-C_vgVPaw.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-D7_1VwC5.js → chunk-SVP7TREG-bA7RXwWn.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-bA7RXwWn.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-bA7RXwWn.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-D5jzVTZ8.js → chunk-TICWLB2K-BG8DAtll.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-BG8DAtll.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BG8DAtll.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-DOhaoLcX.js → chunk-XXDRQBXY-Ces56sud.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-DkVIqOvw.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-DkVIqOvw.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DOsaKgiX.js → cose-bilkent-JH36ORCC-Bwch0qL5.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bwch0qL5.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bwch0qL5.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-CMcLh_wh.js → cynefin-OW5HDTMX-CrAXyA2Z.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-CrAXyA2Z.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CrAXyA2Z.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Dd8bfq9V.js → cynefinDiagram-5FMLGOSQ-YlP0Q93l.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-YlP0Q93l.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-YlP0Q93l.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-BlW7fAi0.js → dagre-GXQ25YYZ-CZ4XcPK9.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-CZ4XcPK9.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CZ4XcPK9.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-B0-tKahG.js → diagram-S7CK7UJ4-c_8HfHGA.js} +1 -1
- package/dist/client/assets/{diagram-S7CK7UJ4-B0-tKahG.js.br → diagram-S7CK7UJ4-c_8HfHGA.js.br} +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-c_8HfHGA.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-BQumj4Dw.js → diagram-UQ7AKVKN-BGtRfxMR.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-BGtRfxMR.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BGtRfxMR.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-Zx_FLmPP.js → diagram-VSXAHHWV-_9yZnhuv.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-_9yZnhuv.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-_9yZnhuv.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-CEZvatNH.js → diagram-VX7I27RA-C_mnc4_u.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-C_mnc4_u.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-C_mnc4_u.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-CJ9IEQ8j.js → diagram-Z3DM3KII-sZ_AiSpj.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-sZ_AiSpj.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-sZ_AiSpj.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-sQQMv3ea.js → ebnfDiagram-PWID7BFC-Dm-BXeqS.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Dm-BXeqS.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Dm-BXeqS.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-D3rMpj7M.js → erDiagram-RLTQ6QDP-Dz4cBepd.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-Dz4cBepd.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Dz4cBepd.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-B6QZiE0-.js → flowDiagram-HODETNUW-CbRG8-WW.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-CbRG8-WW.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CbRG8-WW.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-Bw9bltcu.js → ganttDiagram-EL5Y4UJY-CmBXWlr0.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CmBXWlr0.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CmBXWlr0.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-Cczz34oI.js → gestureArbiter-B26W6rS7.js} +1 -1
- package/dist/client/assets/gestureArbiter-B26W6rS7.js.br +0 -0
- package/dist/client/assets/gestureArbiter-B26W6rS7.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-mdnkxjeD.js → gitGraphDiagram-WWUBYQGX-JW5QMYEk.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-JW5QMYEk.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-JW5QMYEk.js.gz +0 -0
- package/dist/client/assets/index-Ctz-IDjR.css +1 -0
- package/dist/client/assets/index-Ctz-IDjR.css.br +0 -0
- package/dist/client/assets/{index-B4bEMUyn.css.gz → index-Ctz-IDjR.css.gz} +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-DyrPP3Yf.js → infoDiagram-27XIBGKW-CsA6WdPn.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CEPH_ePM.js → ishikawaDiagram-5VMMS53U-CsKPUwTw.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CsKPUwTw.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CsKPUwTw.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-CQ0PjBy7.js → journeyDiagram-3NMN7TZE-DpN98gN0.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DpN98gN0.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DpN98gN0.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-BpQUXA60.js → kanban-definition-UXKFOSKX-RHO--43O.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-RHO--43O.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-RHO--43O.js.gz +0 -0
- package/dist/client/assets/{layout-BdptC7rq.js → layout-Cm_prknW.js} +1 -1
- package/dist/client/assets/layout-Cm_prknW.js.br +0 -0
- package/dist/client/assets/layout-Cm_prknW.js.gz +0 -0
- package/dist/client/assets/{linear-C2cSRCxu.js → linear-BVAsPkvS.js} +1 -1
- package/dist/client/assets/linear-BVAsPkvS.js.br +0 -0
- package/dist/client/assets/linear-BVAsPkvS.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-ClVMFyAU.js → mermaid.core-ufe77Xaz.js} +5 -5
- package/dist/client/assets/mermaid.core-ufe77Xaz.js.br +0 -0
- package/dist/client/assets/mermaid.core-ufe77Xaz.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-D0AFCmsY.js → mindmap-definition-YA3MSWOX-CRyy1U2d.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CRyy1U2d.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CRyy1U2d.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-3_SIzSH7.js +2 -0
- package/dist/client/assets/passwordBootstrap-3_SIzSH7.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-3_SIzSH7.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-BGAh0bwg.js → pegDiagram-XKGWAZYB-DSHgdU9q.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-DSHgdU9q.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DSHgdU9q.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-CDOZUF44.js → pieDiagram-E7YTZNPT-lIUeAuhN.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-lIUeAuhN.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-lIUeAuhN.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-D_Qn6NTU.js → quadrantDiagram-AXDQQJYC-CIgG9vBX.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CIgG9vBX.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CIgG9vBX.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-qUVGWhJN.js → railroadDiagram-O6MQD6OU-DjP8oesA.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DjP8oesA.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DjP8oesA.js.gz +0 -0
- package/dist/client/assets/{rdpSession-hgCM4i-j.js → rdpSession-BshIobdj.js} +1 -1
- package/dist/client/assets/rdpSession-BshIobdj.js.br +0 -0
- package/dist/client/assets/rdpSession-BshIobdj.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-BEMsP11a.js → referenceLongPress-DBJnfFHa.js} +1 -1
- package/dist/client/assets/referenceLongPress-DBJnfFHa.js.br +0 -0
- package/dist/client/assets/referenceLongPress-DBJnfFHa.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-BBQMQuVZ.js → requirementDiagram-BXWQKSXE-Dbvu6IC_.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Dbvu6IC_.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Dbvu6IC_.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CeEExsqW.js → sankeyDiagram-P5KCCOFB-1mZ4s-lv.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CeEExsqW.js.br → sankeyDiagram-P5KCCOFB-1mZ4s-lv.js.br} +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-1mZ4s-lv.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-7ZPO-X0a.js → sequenceDiagram-WJ2MYXX4-Cu3hhzlV.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cu3hhzlV.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cu3hhzlV.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-MBjz7Snm.js → sizeCapture-INFHLROL-xfm69HlR.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-xfm69HlR.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-xfm69HlR.js.gz +0 -0
- package/dist/client/assets/{sparkles-C9ShmGPf.js → sparkles-CDonlwGu.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-DtLp89uP.js → stateDiagram-D77RDMKH-u-bh6mDK.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-u-bh6mDK.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-u-bh6mDK.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BQ_m8Gzd.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-Bc02S1q-.js → swimlanes-42K2YHIH-U8WN__SO.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-U8WN__SO.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-U8WN__SO.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-FDEo3QWw.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-BLLwbOm5.js → timeline-definition-24CTP7MA-CMsoNo4F.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-CMsoNo4F.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-CMsoNo4F.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-i_L8ws6U.js → triangle-alert-BgErMO2V.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-8FcrAdbk.js → vennDiagram-4TSXK5OY-CPE6pVwh.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-8FcrAdbk.js.br → vennDiagram-4TSXK5OY-CPE6pVwh.js.br} +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CPE6pVwh.js.gz +0 -0
- package/dist/client/assets/{videoCompression-RIaDabW4.js → videoCompression-BWkZvEjM.js} +1 -1
- package/dist/client/assets/videoCompression-BWkZvEjM.js.br +0 -0
- package/dist/client/assets/videoCompression-BWkZvEjM.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-paL2wRrJ.js → wardleyDiagram-VM6X3IG4-4lWd-R4N.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-4lWd-R4N.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-4lWd-R4N.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BM5qJ4E7.js → xychartDiagram-S5SC5T6Z-CpKJMCWV.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CpKJMCWV.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CpKJMCWV.js.gz +0 -0
- package/dist/client/index.html +2 -2
- 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/agent/collaborationGroupRoutes.js +15 -0
- package/dist/server/agent/collaborationStore.js +63 -2
- package/dist/server/agent/collaborationTaskRoutes.js +5 -1
- package/dist/server/agent/integrationCli.js +38 -20
- package/dist/server/agent/integrationServer.js +18 -1
- package/dist/server/federation/packets.js +179 -28
- package/dist/server/federation/requestContext.js +6 -1
- package/dist/server/federation/runtime.js +19 -8
- package/dist/server/routes/terminal.js +60 -20
- package/dist/server/utils/autoUpdate.js +55 -8
- package/dist/server/utils/npmUpdate.js +84 -8
- package/dist/server/utils/terminalPasteBuffer.js +66 -0
- package/dist/server/utils/tmuxClientCapabilities.js +4 -0
- package/dist/server/utils/tmuxCopySelection.js +94 -0
- package/dist/server/utils/tmuxEmptyServerProbe.js +15 -0
- package/docs/integrations.md +36 -0
- package/package.json +5 -2
- package/runtime-manifest.json +3 -3
- package/.desktop-dist/main.js +0 -1989
- package/dist/client/assets/App-DEKo4SNL.js +0 -186
- package/dist/client/assets/App-DEKo4SNL.js.br +0 -0
- package/dist/client/assets/App-DEKo4SNL.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-VDM6hrwp.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-VDM6hrwp.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-B64j3i8o.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-B64j3i8o.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DG8TQNqo.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-DG8TQNqo.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DG8TQNqo.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-msQsgVaI.js +0 -2
- package/dist/client/assets/ComputerControlView-Co-TTYas.js.br +0 -0
- package/dist/client/assets/ComputerControlView-Co-TTYas.js.gz +0 -0
- package/dist/client/assets/DagPlayground-kJczdJ6J.js.br +0 -0
- package/dist/client/assets/DagPlayground-kJczdJ6J.js.gz +0 -0
- package/dist/client/assets/DiffLab-BaaioC-q.js.br +0 -0
- package/dist/client/assets/DiffLab-BaaioC-q.js.gz +0 -0
- package/dist/client/assets/DiffReview-Wfck3RIS.js.br +0 -0
- package/dist/client/assets/DiffReview-Wfck3RIS.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-Dblr3wgw.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-Dblr3wgw.js.gz +0 -0
- package/dist/client/assets/DiffViewer-C-utC5Ga.js.br +0 -0
- package/dist/client/assets/DiffViewer-C-utC5Ga.js.gz +0 -0
- package/dist/client/assets/ModelPreview-BBLnHWVQ.js.br +0 -0
- package/dist/client/assets/ModelPreview-BBLnHWVQ.js.gz +0 -0
- package/dist/client/assets/RightSidebar-CUHSFkmZ.js.br +0 -0
- package/dist/client/assets/RightSidebar-CUHSFkmZ.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DDd99QpA.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DDd99QpA.js.gz +0 -0
- package/dist/client/assets/app-CfqF7_nh.js +0 -35
- package/dist/client/assets/app-CfqF7_nh.js.br +0 -0
- package/dist/client/assets/app-CfqF7_nh.js.gz +0 -0
- package/dist/client/assets/arc-C3dck_E0.js.br +0 -0
- package/dist/client/assets/arc-C3dck_E0.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C3bCQc2w.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C3bCQc2w.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-znMn6iCQ.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-znMn6iCQ.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-C0sopIsj.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-C0sopIsj.js.gz +0 -0
- package/dist/client/assets/channel-CMC8t83j.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-DaDCIj30.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DaDCIj30.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DEC_On5x.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DEC_On5x.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D7_1VwC5.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D7_1VwC5.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-D5jzVTZ8.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-D5jzVTZ8.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-ElMHzA-E.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-ElMHzA-E.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DOsaKgiX.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DOsaKgiX.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CMcLh_wh.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CMcLh_wh.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Dd8bfq9V.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Dd8bfq9V.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BlW7fAi0.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BlW7fAi0.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-B0-tKahG.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BQumj4Dw.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BQumj4Dw.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Zx_FLmPP.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Zx_FLmPP.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CEZvatNH.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CEZvatNH.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CJ9IEQ8j.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CJ9IEQ8j.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-sQQMv3ea.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-sQQMv3ea.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-D3rMpj7M.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-D3rMpj7M.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B6QZiE0-.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B6QZiE0-.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bw9bltcu.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bw9bltcu.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-Cczz34oI.js.br +0 -0
- package/dist/client/assets/gestureArbiter-Cczz34oI.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-mdnkxjeD.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-mdnkxjeD.js.gz +0 -0
- package/dist/client/assets/index-B4bEMUyn.css +0 -1
- package/dist/client/assets/index-B4bEMUyn.css.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CEPH_ePM.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CEPH_ePM.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CQ0PjBy7.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CQ0PjBy7.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BpQUXA60.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BpQUXA60.js.gz +0 -0
- package/dist/client/assets/layout-BdptC7rq.js.br +0 -0
- package/dist/client/assets/layout-BdptC7rq.js.gz +0 -0
- package/dist/client/assets/linear-C2cSRCxu.js.br +0 -0
- package/dist/client/assets/linear-C2cSRCxu.js.gz +0 -0
- package/dist/client/assets/mermaid.core-ClVMFyAU.js.br +0 -0
- package/dist/client/assets/mermaid.core-ClVMFyAU.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0AFCmsY.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0AFCmsY.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-R2qlU2Pq.js +0 -2
- package/dist/client/assets/passwordBootstrap-R2qlU2Pq.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-R2qlU2Pq.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BGAh0bwg.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BGAh0bwg.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CDOZUF44.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CDOZUF44.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-D_Qn6NTU.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-D_Qn6NTU.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-qUVGWhJN.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-qUVGWhJN.js.gz +0 -0
- package/dist/client/assets/rdpSession-hgCM4i-j.js.br +0 -0
- package/dist/client/assets/rdpSession-hgCM4i-j.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-BEMsP11a.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BEMsP11a.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BBQMQuVZ.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BBQMQuVZ.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CeEExsqW.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-7ZPO-X0a.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-7ZPO-X0a.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-MBjz7Snm.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-MBjz7Snm.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DtLp89uP.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DtLp89uP.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CIQb4Dp7.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Bc02S1q-.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Bc02S1q-.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-RiKL7eXI.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-BLLwbOm5.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BLLwbOm5.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-8FcrAdbk.js.gz +0 -0
- package/dist/client/assets/videoCompression-RIaDabW4.js.br +0 -0
- package/dist/client/assets/videoCompression-RIaDabW4.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-paL2wRrJ.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-paL2wRrJ.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BM5qJ4E7.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BM5qJ4E7.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
|
|
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 c={};const r=e=>n(e,o),d={module:{uri:o},exports:c,require:r};s[o]=Promise.all(i.map(e=>d[e]||r(e))).then(e=>(a(...e),c))}}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:"f9b108bacc5daf8e78dac614880ef432"},{url:"assets/app-DCFOZi_L.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-Ctz-IDjR.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
|
|
@@ -9,6 +9,21 @@ export function collaborationGroupRoutes(options) {
|
|
|
9
9
|
res.json({ federationVersion: 1, capabilities: { groupRevision: 1, groupPromotion: 1, groupMove: 1, serverCollaboration: options.save ? 1 : undefined },
|
|
10
10
|
groups: options.store.list(), sessions: options.sessions() });
|
|
11
11
|
});
|
|
12
|
+
for (const [verb, archived] of [['archive', true], ['unarchive', false]]) {
|
|
13
|
+
router.post(`/collaboration-groups/:groupId/${verb}`, (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const body = req.body ?? {};
|
|
16
|
+
if (Object.keys(body).some(key => !['expectedUpdatedAt', 'idempotencyKey'].includes(key)))
|
|
17
|
+
throw new CollaborationError('INVALID_GROUP_ARCHIVE_REQUEST', 'Only expectedUpdatedAt and idempotencyKey are accepted', 400);
|
|
18
|
+
const result = options.store.setLocalGroupArchived(req.params.groupId, archived, body.expectedUpdatedAt, body.idempotencyKey);
|
|
19
|
+
res.json(result);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
res.status(error instanceof CollaborationError ? error.httpStatus : 500).json({ code: error instanceof CollaborationError ? error.code : 'GROUP_ARCHIVE_FAILED',
|
|
23
|
+
...(error instanceof CollaborationError && error.details ? error.details : {}), error: error instanceof Error ? error.message : 'Group archive operation failed' });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
12
27
|
router.post('/collaboration-groups', async (req, res) => {
|
|
13
28
|
try {
|
|
14
29
|
const body = req.body ?? {};
|
|
@@ -82,6 +82,7 @@ export class CollaborationStore {
|
|
|
82
82
|
historyAvailable = true;
|
|
83
83
|
groupCreationAvailable = true;
|
|
84
84
|
groupRenamesAvailable = true;
|
|
85
|
+
groupArchivesAvailable = true;
|
|
85
86
|
/** `drawId` is the random source behind newCollaborationId. It is a
|
|
86
87
|
* constructor seam so tests can force a collision and prove the guard
|
|
87
88
|
* below actually skips it; production always uses the CSPRNG default. */
|
|
@@ -126,6 +127,16 @@ export class CollaborationStore {
|
|
|
126
127
|
&& typeof value.applied.name === 'string' && !!value.applied.name.trim() && value.applied.name.length <= 256
|
|
127
128
|
&& !COLLAB_NAME_FORBIDDEN.test(value.applied.name) && Number.isSafeInteger(value.applied.updatedAt) && value.applied.updatedAt >= 0));
|
|
128
129
|
this.document.localGroupRenames = this.groupRenamesAvailable ? renames ?? {} : {};
|
|
130
|
+
const archives = parsed.localGroupArchives;
|
|
131
|
+
this.groupArchivesAvailable = [1, 2].includes(parsed.version ?? 0) && Array.isArray(parsed.groups)
|
|
132
|
+
&& (parsed.localGroupArchivesComplete === undefined || parsed.localGroupArchivesComplete === true)
|
|
133
|
+
&& (archives === undefined || archives !== null && typeof archives === 'object' && !Array.isArray(archives)
|
|
134
|
+
&& Object.entries(archives).every(([key, value]) => /^[a-f0-9]{64}$/.test(key) && value && typeof value.hash === 'string' && /^[a-f0-9]{64}$/.test(value.hash)
|
|
135
|
+
&& value.applied && typeof value.applied.id === 'string' && /^[a-zA-Z0-9._-]{1,80}$/.test(value.applied.id)
|
|
136
|
+
&& typeof value.applied.name === 'string' && !!value.applied.name.trim() && value.applied.name.length <= 256
|
|
137
|
+
&& !COLLAB_NAME_FORBIDDEN.test(value.applied.name) && Number.isSafeInteger(value.applied.updatedAt) && value.applied.updatedAt >= 0
|
|
138
|
+
&& (value.applied.archivedAt === undefined || Number.isSafeInteger(value.applied.archivedAt) && value.applied.archivedAt >= 0)));
|
|
139
|
+
this.document.localGroupArchives = this.groupArchivesAvailable ? archives ?? {} : {};
|
|
129
140
|
this.document.sequence = Math.max(parsed.sequence ?? 0, this.document.sequence ?? 0);
|
|
130
141
|
this.document.idempotency = parsed.idempotency ?? {};
|
|
131
142
|
this.document.consumers = parsed.consumers ?? {};
|
|
@@ -138,10 +149,12 @@ export class CollaborationStore {
|
|
|
138
149
|
this.historyAvailable = error.code === 'ENOENT';
|
|
139
150
|
this.groupCreationAvailable = this.historyAvailable;
|
|
140
151
|
this.groupRenamesAvailable = this.historyAvailable;
|
|
152
|
+
this.groupArchivesAvailable = this.historyAvailable;
|
|
141
153
|
}
|
|
142
154
|
this.document.terminalHistoryComplete = this.historyAvailable;
|
|
143
155
|
this.document.localGroupRequestsComplete = this.groupCreationAvailable;
|
|
144
156
|
this.document.localGroupRenamesComplete = this.groupRenamesAvailable;
|
|
157
|
+
this.document.localGroupArchivesComplete = this.groupArchivesAvailable;
|
|
145
158
|
this.persistedDocument = JSON.stringify(this.document);
|
|
146
159
|
}
|
|
147
160
|
list() {
|
|
@@ -248,7 +261,54 @@ export class CollaborationStore {
|
|
|
248
261
|
throw new CollaborationError('GROUP_NOT_LOCAL', 'Administrator metadata operations require a local group', 409);
|
|
249
262
|
if (!Number.isSafeInteger(group.updatedAt) || group.updatedAt < 0)
|
|
250
263
|
throw new CollaborationError('GROUP_STATE_UNCONFIRMED', 'Group revision is unavailable', 409);
|
|
251
|
-
return { id: group.id, name: group.name, updatedAt: group.updatedAt };
|
|
264
|
+
return { id: group.id, name: group.name, updatedAt: group.updatedAt, ...(group.archivedAt !== undefined ? { archivedAt: group.archivedAt } : {}) };
|
|
265
|
+
}
|
|
266
|
+
listLocalGroupMetadata(includeArchived = false) {
|
|
267
|
+
return this.list().filter(group => !group.federated && (includeArchived || group.archivedAt === undefined))
|
|
268
|
+
.map(group => ({ id: group.id, name: group.name, updatedAt: group.updatedAt,
|
|
269
|
+
...(group.archivedAt !== undefined ? { archivedAt: group.archivedAt } : {}) }));
|
|
270
|
+
}
|
|
271
|
+
/** Archive is a reversible discovery flag; it never changes membership or message routing. */
|
|
272
|
+
setLocalGroupArchived(id, archived, expectedUpdatedAt, key) {
|
|
273
|
+
if (typeof id !== 'string' || !/^[a-zA-Z0-9._-]{1,80}$/.test(id))
|
|
274
|
+
throw new CollaborationError('INVALID_GROUP_ID', 'An exact group ID is required', 400);
|
|
275
|
+
if (typeof archived !== 'boolean')
|
|
276
|
+
throw new CollaborationError('INVALID_GROUP_ARCHIVE_REQUEST', 'Archive state must be explicit', 400);
|
|
277
|
+
if (!Number.isSafeInteger(expectedUpdatedAt) || expectedUpdatedAt < 0)
|
|
278
|
+
throw new CollaborationError('INVALID_GROUP_VERSION', 'expected_updated_at must be a non-negative safe integer', 400);
|
|
279
|
+
const requestKey = localGroupRequestKey(key);
|
|
280
|
+
if (!this.groupArchivesAvailable)
|
|
281
|
+
throw new CollaborationError('GROUP_ARCHIVE_STATE_UNCONFIRMED', 'Archive journal is unavailable; reconcile persisted state before changing group visibility', 409);
|
|
282
|
+
const hash = crypto.createHash('sha256').update(JSON.stringify(['archive-local-group', id, archived, expectedUpdatedAt])).digest('hex');
|
|
283
|
+
const previous = this.document.localGroupArchives?.[requestKey];
|
|
284
|
+
if (previous) {
|
|
285
|
+
if (previous.hash !== hash)
|
|
286
|
+
throw new CollaborationError('IDEMPOTENCY_CONFLICT', 'This administrator archive key was used with different parameters', 409);
|
|
287
|
+
const target = this.getGroup(id);
|
|
288
|
+
if (!target || target.deleted || target.federated)
|
|
289
|
+
throw new CollaborationError('GROUP_ARCHIVE_TARGET_UNAVAILABLE', 'Original local archive target is unavailable; no replacement is created', 409);
|
|
290
|
+
const group = this.getLocalGroupMetadata(id);
|
|
291
|
+
this.persist(true);
|
|
292
|
+
return { group, applied: { ...previous.applied }, replayed: true };
|
|
293
|
+
}
|
|
294
|
+
const current = this.getLocalGroupMetadata(id);
|
|
295
|
+
if (current.updatedAt !== expectedUpdatedAt)
|
|
296
|
+
throw new CollaborationError('GROUP_CHANGED', 'Group changed; read its current revision before deciding again', 409, { currentUpdatedAt: current.updatedAt });
|
|
297
|
+
if (Object.keys(this.document.localGroupArchives ?? {}).length >= 2000)
|
|
298
|
+
throw new CollaborationError('GROUP_ARCHIVE_LIMIT', 'Administrator archive journal has reached its 2000-record limit', 409);
|
|
299
|
+
const target = this.getGroup(id);
|
|
300
|
+
const currentlyArchived = target.archivedAt !== undefined;
|
|
301
|
+
const changed = currentlyArchived !== archived;
|
|
302
|
+
const updatedAt = changed ? Math.max(Date.now(), current.updatedAt + 1) : current.updatedAt;
|
|
303
|
+
if (!Number.isSafeInteger(updatedAt))
|
|
304
|
+
throw new CollaborationError('GROUP_STATE_UNCONFIRMED', 'Group revision cannot advance safely', 409);
|
|
305
|
+
const archivedAt = archived ? currentlyArchived ? target.archivedAt : Date.now() : undefined;
|
|
306
|
+
const applied = { id, name: target.name, updatedAt, ...(archivedAt !== undefined ? { archivedAt } : {}) };
|
|
307
|
+
this.document = { ...this.document, groups: this.document.groups.map(group => group.id === id
|
|
308
|
+
? { ...group, updatedAt, ...(archivedAt !== undefined ? { archivedAt } : { archivedAt: undefined }) } : group),
|
|
309
|
+
localGroupArchives: { ...this.document.localGroupArchives, [requestKey]: { hash, applied } } };
|
|
310
|
+
this.persist(true);
|
|
311
|
+
return { group: { ...applied }, applied: { ...applied }, replayed: false };
|
|
252
312
|
}
|
|
253
313
|
/** Display metadata and its original receipt share a durable commit. */
|
|
254
314
|
renameLocalGroup(id, value, expectedUpdatedAt, key) {
|
|
@@ -282,7 +342,8 @@ export class CollaborationStore {
|
|
|
282
342
|
const updatedAt = name === current.name ? current.updatedAt : Math.max(Date.now(), current.updatedAt + 1);
|
|
283
343
|
if (!Number.isSafeInteger(updatedAt))
|
|
284
344
|
throw new CollaborationError('GROUP_STATE_UNCONFIRMED', 'Group revision cannot advance safely', 409);
|
|
285
|
-
const
|
|
345
|
+
const target = this.getGroup(id);
|
|
346
|
+
const applied = { id, name, updatedAt, ...(target.archivedAt !== undefined ? { archivedAt: target.archivedAt } : {}) };
|
|
286
347
|
this.document = { ...this.document, groups: this.document.groups.map(group => group.id === id ? { ...group, name, updatedAt } : group),
|
|
287
348
|
localGroupRenames: { ...this.document.localGroupRenames, [requestKey]: { hash, applied } } };
|
|
288
349
|
this.persist(true);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { CollaborationError } from './collaborationProtocol.js';
|
|
3
3
|
import { assertPeerRegistrationAuthority } from './collaborationPeerTransport.js';
|
|
4
|
+
import { hasEncryptedServiceAccess } from '../federation/requestContext.js';
|
|
4
5
|
export function collaborationTaskRoutes(options) {
|
|
5
6
|
const router = Router();
|
|
6
7
|
const groupId = (value) => {
|
|
@@ -15,8 +16,11 @@ export function collaborationTaskRoutes(options) {
|
|
|
15
16
|
if (!tasks || !peers)
|
|
16
17
|
throw new CollaborationError('TASK_SERVICE_STARTING', '任务服务正在启动,请稍后重试', 503);
|
|
17
18
|
if (!options.agent) {
|
|
19
|
+
// Reading the inbox does not register peers or create durable authority.
|
|
20
|
+
// Open-mode full-service viewers may read; mutations still require admin.
|
|
18
21
|
try {
|
|
19
|
-
|
|
22
|
+
if (!(['GET', 'HEAD'].includes(req.method) && hasEncryptedServiceAccess(req)))
|
|
23
|
+
assertPeerRegistrationAuthority(req);
|
|
20
24
|
}
|
|
21
25
|
catch {
|
|
22
26
|
throw new CollaborationError('TASK_PERMISSION_DENIED', '当前连接无权处理协作任务', 403);
|
|
@@ -26,6 +26,14 @@ export const INTEGRATION_HELP = `td integration — local scoped integration adm
|
|
|
26
26
|
GROUP_CHANGED, GROUP_NOT_FOUND, GROUP_NOT_LOCAL, GROUP_STATE_UNCONFIRMED,
|
|
27
27
|
GROUP_RENAME_TARGET_UNAVAILABLE, GROUP_RENAME_STATE_UNCONFIRMED,
|
|
28
28
|
GROUP_RENAME_LIMIT, ADMIN_LOCAL_GROUP_RENAME_UNSUPPORTED.
|
|
29
|
+
group archive|unarchive <exact-group-id> --expected-updated-at <N> --idempotency-key <key>
|
|
30
|
+
Administrator only; minimum CLI/running service 1.4.323; admin_local_group_archive=true.
|
|
31
|
+
Reversible discovery metadata only. Default groups list hides archived groups;
|
|
32
|
+
groups --include-archived lists them. group get always includes archivedAt when set.
|
|
33
|
+
Membership, principals, sessions, runtime, messages and history are unchanged.
|
|
34
|
+
Retry uncertain results with the same operation, revision and key; GROUP_CHANGED
|
|
35
|
+
requires rereading current metadata and choosing a new key. Archive receipts are
|
|
36
|
+
retained separately from the immutable group-create snapshot.
|
|
29
37
|
group create --name <name> --idempotency-key <key>
|
|
30
38
|
Administrator only; create an empty local group without a terminal source.
|
|
31
39
|
Minimum CLI/running service 1.4.318; admin_local_group_create=true.
|
|
@@ -125,29 +133,33 @@ export async function runIntegrationAdmin(argv, port, adminToken, write) {
|
|
|
125
133
|
return 0;
|
|
126
134
|
}
|
|
127
135
|
const socketPath = integrationSocketPath(port);
|
|
128
|
-
if (action === 'group' && ['get', 'rename'].includes(argv[1])) {
|
|
136
|
+
if (action === 'group' && ['get', 'rename', 'archive', 'unarchive'].includes(argv[1])) {
|
|
129
137
|
const operation = argv[1], id = argv[2], args = {};
|
|
130
|
-
const usage = 'Usage: td integration group get <exact-id> | group rename <exact-id> --name <name> --expected-updated-at <N> --idempotency-key <key>';
|
|
138
|
+
const usage = 'Usage: td integration group get <exact-id> | group rename <exact-id> --name <name> --expected-updated-at <N> --idempotency-key <key> | group archive|unarchive <exact-id> --expected-updated-at <N> --idempotency-key <key>';
|
|
131
139
|
if (!id || !/^[a-zA-Z0-9._-]{1,80}$/.test(id))
|
|
132
140
|
throw new CollaborationError('INVALID_GROUP_ID', usage, 400);
|
|
133
141
|
if (operation === 'get' && argv.length !== 3)
|
|
134
142
|
throw new CollaborationError('INVALID_GROUP_RENAME_REQUEST', usage, 400);
|
|
135
|
-
if (operation
|
|
143
|
+
if (operation !== 'get') {
|
|
136
144
|
for (let i = 3; i < argv.length; i += 2) {
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
const allowed = operation === 'rename' ? ['--name', '--expected-updated-at', '--idempotency-key'] : ['--expected-updated-at', '--idempotency-key'];
|
|
146
|
+
if (!allowed.includes(argv[i]) || !argv[i + 1] || Object.hasOwn(args, argv[i]))
|
|
147
|
+
throw new CollaborationError(operation === 'rename' ? 'INVALID_GROUP_RENAME_REQUEST' : 'INVALID_GROUP_ARCHIVE_REQUEST', usage, 400);
|
|
139
148
|
args[argv[i]] = argv[i + 1];
|
|
140
149
|
}
|
|
141
|
-
if (Object.keys(args).length !== 3 || !args['--name']?.trim() || !args['--idempotency-key']?.trim()
|
|
142
|
-
|
|
150
|
+
if (operation === 'rename' ? Object.keys(args).length !== 3 || !args['--name']?.trim() || !args['--idempotency-key']?.trim()
|
|
151
|
+
: Object.keys(args).length !== 2 || !args['--idempotency-key']?.trim())
|
|
152
|
+
throw new CollaborationError(operation === 'rename' ? 'INVALID_GROUP_RENAME_REQUEST' : 'INVALID_GROUP_ARCHIVE_REQUEST', usage, 400);
|
|
143
153
|
if (!/^(0|[1-9][0-9]*)$/.test(args['--expected-updated-at']) || !Number.isSafeInteger(Number(args['--expected-updated-at'])))
|
|
144
154
|
throw new CollaborationError('INVALID_GROUP_VERSION', usage, 400);
|
|
145
155
|
}
|
|
146
156
|
const capabilities = parsed(await request(socketPath, 'GET', '/admin/capabilities', undefined, headers(undefined, adminToken)));
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
const isArchive = operation === 'archive' || operation === 'unarchive';
|
|
158
|
+
if (capabilities[isArchive ? 'admin_local_group_archive' : 'admin_local_group_rename'] !== true)
|
|
159
|
+
throw new CollaborationError(isArchive ? 'ADMIN_LOCAL_GROUP_ARCHIVE_UNSUPPORTED' : 'ADMIN_LOCAL_GROUP_RENAME_UNSUPPORTED', `CLI and running service must support this operation (minimum 1.4.${isArchive ? '323' : '319'})`, 409);
|
|
160
|
+
const body = operation === 'get' ? undefined : { ...(operation === 'rename' ? { name: args['--name'] } : {}), expected_updated_at: Number(args['--expected-updated-at']), idempotency_key: args['--idempotency-key'] };
|
|
161
|
+
const suffix = operation === 'get' ? '' : `/${operation}`;
|
|
162
|
+
write(JSON.stringify(parsed(await request(socketPath, body ? 'POST' : 'GET', `/admin/groups/${encodeURIComponent(id)}${suffix}`, body, headers(undefined, adminToken)))));
|
|
151
163
|
return 0;
|
|
152
164
|
}
|
|
153
165
|
if (action === 'group') {
|
|
@@ -213,16 +225,22 @@ export async function runIntegrationAdmin(argv, port, adminToken, write) {
|
|
|
213
225
|
write(JSON.stringify(parsed(await request(socketPath, 'GET', `/admin/sessions/${encodeURIComponent(argv[1])}/restore-diagnostics`, undefined, headers(undefined, adminToken)))));
|
|
214
226
|
return 0;
|
|
215
227
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
if (action !== 'groups' && action !== 'list')
|
|
229
|
+
for (let index = 1; index < argv.length; index += 2) {
|
|
230
|
+
const flag = argv[index];
|
|
231
|
+
if (!['--file', '--credential-file'].includes(flag) || !argv[index + 1] || options[flag])
|
|
232
|
+
throw new Error('Invalid integration arguments');
|
|
233
|
+
options[flag] = argv[index + 1];
|
|
234
|
+
}
|
|
222
235
|
if (action === 'groups' || action === 'list') {
|
|
223
|
-
if (argv.length !== 1)
|
|
224
|
-
throw new Error('
|
|
225
|
-
|
|
236
|
+
if (action === 'list' && argv.length !== 1 || action === 'groups' && argv.length > 2 || action === 'groups' && argv.length === 2 && argv[1] !== '--include-archived')
|
|
237
|
+
throw new Error('Usage: td integration groups [--include-archived] | list');
|
|
238
|
+
if (action === 'groups' && argv[1] === '--include-archived') {
|
|
239
|
+
const capabilities = parsed(await request(socketPath, 'GET', '/admin/capabilities', undefined, headers(undefined, adminToken)));
|
|
240
|
+
if (capabilities.admin_local_group_archive !== true)
|
|
241
|
+
throw new CollaborationError('ADMIN_LOCAL_GROUP_ARCHIVE_UNSUPPORTED', 'CLI and running service must support archived group discovery (minimum 1.4.323)', 409);
|
|
242
|
+
}
|
|
243
|
+
write(JSON.stringify(parsed(await request(socketPath, 'GET', action === 'groups' ? `/admin/groups${argv[1] ? '?include_archived=true' : ''}` : '/admin/principals', undefined, headers(undefined, adminToken)))));
|
|
226
244
|
return 0;
|
|
227
245
|
}
|
|
228
246
|
if (action === 'update') {
|
|
@@ -68,7 +68,7 @@ export class IntegrationRuntime {
|
|
|
68
68
|
if (!expected.length || given.length !== expected.length || !timingSafeEqual(given, expected))
|
|
69
69
|
integrationError('INTEGRATION_ADMIN_REQUIRED', 'Local administration credential is required', 401);
|
|
70
70
|
};
|
|
71
|
-
app.get('/admin/groups', run(req => { admin(req); return { groups: options.messages.
|
|
71
|
+
app.get('/admin/groups', run(req => { admin(req); return { groups: options.messages.listLocalGroupMetadata(req.query.include_archived === 'true') }; }));
|
|
72
72
|
app.get('/admin/groups/:id', run(req => { admin(req); return { group: options.messages.getLocalGroupMetadata(req.params.id) }; }));
|
|
73
73
|
app.post('/admin/groups/:id/rename', run(req => {
|
|
74
74
|
admin(req);
|
|
@@ -78,6 +78,22 @@ export class IntegrationRuntime {
|
|
|
78
78
|
}
|
|
79
79
|
return options.messages.renameLocalGroup(req.params.id, body.name, body.expected_updated_at, body.idempotency_key);
|
|
80
80
|
}));
|
|
81
|
+
app.post('/admin/groups/:id/archive', run(req => {
|
|
82
|
+
admin(req);
|
|
83
|
+
const body = req.body;
|
|
84
|
+
if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).some(key => !['expected_updated_at', 'idempotency_key'].includes(key))) {
|
|
85
|
+
integrationError('INVALID_GROUP_ARCHIVE_REQUEST', 'Specify only expected_updated_at and idempotency_key');
|
|
86
|
+
}
|
|
87
|
+
return options.messages.setLocalGroupArchived(req.params.id, true, body.expected_updated_at, body.idempotency_key);
|
|
88
|
+
}));
|
|
89
|
+
app.post('/admin/groups/:id/unarchive', run(req => {
|
|
90
|
+
admin(req);
|
|
91
|
+
const body = req.body;
|
|
92
|
+
if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).some(key => !['expected_updated_at', 'idempotency_key'].includes(key))) {
|
|
93
|
+
integrationError('INVALID_GROUP_ARCHIVE_REQUEST', 'Specify only expected_updated_at and idempotency_key');
|
|
94
|
+
}
|
|
95
|
+
return options.messages.setLocalGroupArchived(req.params.id, false, body.expected_updated_at, body.idempotency_key);
|
|
96
|
+
}));
|
|
81
97
|
app.post('/admin/groups', run(req => {
|
|
82
98
|
admin(req);
|
|
83
99
|
const body = req.body;
|
|
@@ -254,6 +270,7 @@ export class IntegrationRuntime {
|
|
|
254
270
|
session_close: true, session_close_min_version: '1.4.315',
|
|
255
271
|
admin_local_group_create: true, admin_local_group_create_min_version: '1.4.318',
|
|
256
272
|
admin_local_group_rename: true, admin_local_group_rename_min_version: '1.4.319',
|
|
273
|
+
admin_local_group_archive: true, admin_local_group_archive_min_version: '1.4.323',
|
|
257
274
|
integration_policy_update: true,
|
|
258
275
|
session_restore_diagnostics: true,
|
|
259
276
|
native_identity_linux_flock_owner: process.platform === 'linux',
|
|
@@ -8,6 +8,16 @@ export class AsyncQueue {
|
|
|
8
8
|
constructor(limit = 64) {
|
|
9
9
|
this.limit = limit;
|
|
10
10
|
}
|
|
11
|
+
/** Try to enqueue without changing the fail-closed behavior of push(). */
|
|
12
|
+
tryPush(value) {
|
|
13
|
+
if (this.ended)
|
|
14
|
+
throw new Error('Channel closed');
|
|
15
|
+
if (this.items.length >= this.limit)
|
|
16
|
+
return false;
|
|
17
|
+
this.items.push(value);
|
|
18
|
+
this.wake?.();
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
11
21
|
push(value) {
|
|
12
22
|
if (this.ended)
|
|
13
23
|
throw new Error('Channel closed');
|
|
@@ -18,7 +28,9 @@ export class AsyncQueue {
|
|
|
18
28
|
this.items.push(value);
|
|
19
29
|
this.wake?.();
|
|
20
30
|
}
|
|
31
|
+
get size() { return this.items.length; }
|
|
21
32
|
end(error) { this.ended = true; this.error ??= error; this.wake?.(); }
|
|
33
|
+
clear() { this.items.length = 0; }
|
|
22
34
|
async *[Symbol.asyncIterator]() {
|
|
23
35
|
while (true) {
|
|
24
36
|
if (this.error)
|
|
@@ -35,7 +47,13 @@ export class AsyncQueue {
|
|
|
35
47
|
}
|
|
36
48
|
}
|
|
37
49
|
}
|
|
50
|
+
export class PacketQueueBackpressureError extends Error {
|
|
51
|
+
code = 'PACKET_QUEUE_BACKPRESSURE';
|
|
52
|
+
constructor(message = 'Record queue capacity exceeded') { super(message); this.name = 'PacketQueueBackpressureError'; }
|
|
53
|
+
}
|
|
38
54
|
const MAX_PACKET = 2 * 1024 * 1024;
|
|
55
|
+
const MAX_BUFFERED_PACKET_BYTES = MAX_PACKET * 2 + 8;
|
|
56
|
+
const READ_BLOCK_BYTES = 64 * 1024;
|
|
39
57
|
const encoder = new TextEncoder();
|
|
40
58
|
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
41
59
|
/** Application records live entirely inside Noise; transport routing never sees them. */
|
|
@@ -43,24 +61,31 @@ export class PacketChannel {
|
|
|
43
61
|
duplex;
|
|
44
62
|
outgoing = new AsyncQueue(256);
|
|
45
63
|
outgoingBytes = 0;
|
|
64
|
+
queuedBytesById = new Map();
|
|
65
|
+
closed = false;
|
|
46
66
|
get hasPendingWrites() { return this.outgoingBytes > 0; }
|
|
67
|
+
queuedBytesFor(id) { return this.queuedBytesById.get(id) ?? 0; }
|
|
47
68
|
done;
|
|
48
69
|
constructor(duplex) {
|
|
49
70
|
this.duplex = duplex;
|
|
50
71
|
const self = this;
|
|
51
72
|
this.done = duplex.sink((async function* () {
|
|
52
|
-
for await (const
|
|
73
|
+
for await (const packet of self.outgoing) {
|
|
53
74
|
try {
|
|
54
|
-
yield bytes;
|
|
75
|
+
yield packet.bytes;
|
|
55
76
|
}
|
|
56
77
|
finally {
|
|
57
|
-
self.
|
|
78
|
+
self.releaseQueuedPacket(packet);
|
|
58
79
|
}
|
|
59
80
|
}
|
|
60
81
|
})());
|
|
61
|
-
void this.done.
|
|
82
|
+
void this.done.then(() => this.discardPending(), error => this.discardPending(error instanceof Error ? error : new Error('Transport failed')));
|
|
62
83
|
}
|
|
63
84
|
send(packet) {
|
|
85
|
+
// The item-count cap protects against floods of tiny records. Check it
|
|
86
|
+
// before encoding and keep this queue alive when it is temporarily full.
|
|
87
|
+
if (this.outgoing.size >= 256)
|
|
88
|
+
throw new PacketQueueBackpressureError('Record queue item limit exceeded');
|
|
64
89
|
const body = encoder.encode(JSON.stringify(packet));
|
|
65
90
|
if (body.length > MAX_PACKET)
|
|
66
91
|
throw new Error('Record too large');
|
|
@@ -68,40 +93,166 @@ export class PacketChannel {
|
|
|
68
93
|
new DataView(bytes.buffer).setUint32(0, body.length);
|
|
69
94
|
bytes.set(body, 4);
|
|
70
95
|
if (this.outgoingBytes + bytes.byteLength > 4 * 1024 * 1024)
|
|
71
|
-
throw new
|
|
96
|
+
throw new PacketQueueBackpressureError('Record queue byte budget exceeded');
|
|
97
|
+
const queued = { id: packet.id, bytes };
|
|
98
|
+
if (!this.outgoing.tryPush(queued))
|
|
99
|
+
throw new PacketQueueBackpressureError('Record queue item limit exceeded');
|
|
72
100
|
this.outgoingBytes += bytes.byteLength;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
this.queuedBytesById.set(packet.id, this.queuedBytesFor(packet.id) + bytes.byteLength);
|
|
102
|
+
}
|
|
103
|
+
releaseQueuedPacket(packet) {
|
|
104
|
+
this.outgoingBytes = Math.max(0, this.outgoingBytes - packet.bytes.byteLength);
|
|
105
|
+
const current = this.queuedBytesById.get(packet.id);
|
|
106
|
+
if (current === undefined)
|
|
107
|
+
return;
|
|
108
|
+
const remaining = current - packet.bytes.byteLength;
|
|
109
|
+
if (remaining > 0)
|
|
110
|
+
this.queuedBytesById.set(packet.id, remaining);
|
|
111
|
+
else
|
|
112
|
+
this.queuedBytesById.delete(packet.id);
|
|
113
|
+
}
|
|
114
|
+
close(error) {
|
|
115
|
+
// Closing the duplex stops transport delivery, so queued records cannot
|
|
116
|
+
// be described as drained. Discard them and release their accounting.
|
|
117
|
+
this.discardPending(error);
|
|
118
|
+
}
|
|
119
|
+
abort(error) {
|
|
120
|
+
this.discardPending(error);
|
|
121
|
+
}
|
|
122
|
+
discardPending(error) {
|
|
123
|
+
if (this.closed)
|
|
124
|
+
return;
|
|
125
|
+
this.closed = true;
|
|
126
|
+
this.outgoing.clear();
|
|
127
|
+
this.outgoing.end(error);
|
|
128
|
+
this.outgoingBytes = 0;
|
|
129
|
+
this.queuedBytesById.clear();
|
|
130
|
+
this.duplex.close?.(error);
|
|
80
131
|
}
|
|
81
|
-
close(error) { this.outgoing.end(error); this.duplex.close?.(error); }
|
|
82
132
|
async *read() {
|
|
83
|
-
|
|
133
|
+
const segments = [];
|
|
134
|
+
let firstSegment = 0;
|
|
135
|
+
let bufferedBytes = 0;
|
|
136
|
+
let spareBlock;
|
|
137
|
+
const parseFrame = (frame) => {
|
|
138
|
+
const packet = JSON.parse(decoder.decode(frame.subarray(4)));
|
|
139
|
+
if (!packet || typeof packet !== 'object' || typeof packet.id !== 'string' || typeof packet.type !== 'string')
|
|
140
|
+
throw new Error('Invalid record');
|
|
141
|
+
return packet;
|
|
142
|
+
};
|
|
143
|
+
const append = (chunk) => {
|
|
144
|
+
let offset = 0;
|
|
145
|
+
while (offset < chunk.byteLength) {
|
|
146
|
+
let tail = segments[segments.length - 1];
|
|
147
|
+
if (!tail || tail.end === tail.bytes.byteLength) {
|
|
148
|
+
tail = { bytes: spareBlock ?? new Uint8Array(READ_BLOCK_BYTES), start: 0, end: 0 };
|
|
149
|
+
spareBlock = undefined;
|
|
150
|
+
segments.push(tail);
|
|
151
|
+
}
|
|
152
|
+
const take = Math.min(chunk.byteLength - offset, tail.bytes.byteLength - tail.end);
|
|
153
|
+
tail.bytes.set(chunk.subarray(offset, offset + take), tail.end);
|
|
154
|
+
tail.end += take;
|
|
155
|
+
offset += take;
|
|
156
|
+
}
|
|
157
|
+
bufferedBytes += chunk.byteLength;
|
|
158
|
+
};
|
|
159
|
+
const byteAt = (offset) => {
|
|
160
|
+
let index = firstSegment;
|
|
161
|
+
let position = offset;
|
|
162
|
+
while (index < segments.length) {
|
|
163
|
+
const segment = segments[index];
|
|
164
|
+
if (!segment) {
|
|
165
|
+
index++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const available = segment.end - segment.start;
|
|
169
|
+
if (position < available)
|
|
170
|
+
return segment.bytes[segment.start + position];
|
|
171
|
+
position -= available;
|
|
172
|
+
index++;
|
|
173
|
+
}
|
|
174
|
+
throw new Error('Incomplete record header');
|
|
175
|
+
};
|
|
176
|
+
const advance = (length) => {
|
|
177
|
+
bufferedBytes -= length;
|
|
178
|
+
while (length > 0) {
|
|
179
|
+
const segment = segments[firstSegment];
|
|
180
|
+
if (!segment) {
|
|
181
|
+
firstSegment++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const take = Math.min(length, segment.end - segment.start);
|
|
185
|
+
segment.start += take;
|
|
186
|
+
length -= take;
|
|
187
|
+
if (segment.start === segment.end) {
|
|
188
|
+
if (!spareBlock && segment.bytes.byteLength === READ_BLOCK_BYTES)
|
|
189
|
+
spareBlock = segment.bytes;
|
|
190
|
+
segments[firstSegment] = undefined;
|
|
191
|
+
firstSegment++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (bufferedBytes === 0) {
|
|
195
|
+
segments.length = 0;
|
|
196
|
+
firstSegment = 0;
|
|
197
|
+
spareBlock = undefined;
|
|
198
|
+
}
|
|
199
|
+
else if (firstSegment >= 64 && firstSegment * 2 >= segments.length) {
|
|
200
|
+
segments.splice(0, firstSegment);
|
|
201
|
+
firstSegment = 0;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const recordBytes = (length) => {
|
|
205
|
+
const first = segments[firstSegment];
|
|
206
|
+
if (!first)
|
|
207
|
+
throw new Error('Incomplete record body');
|
|
208
|
+
const available = first.end - first.start;
|
|
209
|
+
if (available >= length) {
|
|
210
|
+
const result = first.bytes.subarray(first.start, first.start + length);
|
|
211
|
+
advance(length);
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
const result = new Uint8Array(length);
|
|
215
|
+
let index = firstSegment;
|
|
216
|
+
let written = 0;
|
|
217
|
+
while (written < length) {
|
|
218
|
+
const segment = segments[index];
|
|
219
|
+
if (!segment) {
|
|
220
|
+
index++;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const take = Math.min(length - written, segment.end - segment.start);
|
|
224
|
+
result.set(segment.bytes.subarray(segment.start, segment.start + take), written);
|
|
225
|
+
written += take;
|
|
226
|
+
index++;
|
|
227
|
+
}
|
|
228
|
+
advance(length);
|
|
229
|
+
return result;
|
|
230
|
+
};
|
|
84
231
|
for await (const chunk of this.duplex.source) {
|
|
85
|
-
if (
|
|
232
|
+
if (bufferedBytes + chunk.byteLength > MAX_BUFFERED_PACKET_BYTES)
|
|
86
233
|
throw new Error('Record buffer exceeded');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
234
|
+
if (bufferedBytes === 0 && chunk.byteLength >= 5) {
|
|
235
|
+
const size = ((chunk[0] * 0x1000000) + (chunk[1] << 16) + (chunk[2] << 8) + chunk[3]) >>> 0;
|
|
236
|
+
if (size > MAX_PACKET || size === 0)
|
|
237
|
+
throw new Error('Invalid record size');
|
|
238
|
+
if (chunk.byteLength === size + 4) {
|
|
239
|
+
yield parseFrame(chunk);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (chunk.byteLength)
|
|
244
|
+
append(chunk);
|
|
245
|
+
while (bufferedBytes >= 4) {
|
|
246
|
+
const size = ((byteAt(0) * 0x1000000) + (byteAt(1) << 16) + (byteAt(2) << 8) + byteAt(3)) >>> 0;
|
|
93
247
|
if (size > MAX_PACKET || size === 0)
|
|
94
248
|
throw new Error('Invalid record size');
|
|
95
|
-
if (
|
|
249
|
+
if (bufferedBytes < size + 4)
|
|
96
250
|
break;
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
throw new Error('Invalid record');
|
|
100
|
-
yield packet;
|
|
101
|
-
pending = pending.slice(size + 4);
|
|
251
|
+
const frame = recordBytes(size + 4);
|
|
252
|
+
yield parseFrame(frame);
|
|
102
253
|
}
|
|
103
254
|
}
|
|
104
|
-
if (
|
|
255
|
+
if (bufferedBytes)
|
|
105
256
|
throw new Error('Truncated record');
|
|
106
257
|
}
|
|
107
258
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
// Object identity is deliberately used instead of a remotely forgeable header.
|
|
2
2
|
const verified = new WeakSet();
|
|
3
3
|
const subjects = new WeakMap();
|
|
4
|
-
|
|
4
|
+
const serviceAccess = new WeakSet();
|
|
5
|
+
export function markEncryptedRequest(request, subjectId, fullService = false) {
|
|
5
6
|
verified.add(request);
|
|
6
7
|
if (subjectId)
|
|
7
8
|
subjects.set(request, subjectId);
|
|
9
|
+
if (fullService)
|
|
10
|
+
serviceAccess.add(request);
|
|
8
11
|
}
|
|
9
12
|
export function isEncryptedRequest(request) { return verified.has(request); }
|
|
10
13
|
export function encryptedRequestSubject(request) { return subjects.get(request); }
|
|
14
|
+
/** Set only by the target's authenticated internal dispatcher, never a page header. */
|
|
15
|
+
export function hasEncryptedServiceAccess(request) { return serviceAccess.has(request); }
|