termdock 1.4.178 → 1.4.180
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/.desktop-dist/main.js +1 -1
- package/dist/client/assets/App-DBds0RoW.js +150 -0
- package/dist/client/assets/App-DBds0RoW.js.br +0 -0
- package/dist/client/assets/App-DBds0RoW.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-BNWqAnqc.js → ChangeWalkthroughPanel-DonkfMV2.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-DonkfMV2.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DonkfMV2.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-IlTGwr-I.js → DagPlayground-CYL8vhaU.js} +1 -1
- package/dist/client/assets/DagPlayground-CYL8vhaU.js.br +0 -0
- package/dist/client/assets/DagPlayground-CYL8vhaU.js.gz +0 -0
- package/dist/client/assets/{DiffLab-CwhNpJvo.js → DiffLab-COUjebir.js} +1 -1
- package/dist/client/assets/DiffLab-COUjebir.js.br +0 -0
- package/dist/client/assets/DiffLab-COUjebir.js.gz +0 -0
- package/dist/client/assets/{DiffReview-DyhINv0F.js → DiffReview-DqiRXomQ.js} +1 -1
- package/dist/client/assets/DiffReview-DqiRXomQ.js.br +0 -0
- package/dist/client/assets/DiffReview-DqiRXomQ.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-D9Kt5VwB.js → DiffReviewLab-Aqib8Qr6.js} +1 -1
- package/dist/client/assets/DiffReviewLab-Aqib8Qr6.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-Aqib8Qr6.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-3zz2PI-8.js → DiffViewer-KR2GhX72.js} +1 -1
- package/dist/client/assets/DiffViewer-KR2GhX72.js.br +0 -0
- package/dist/client/assets/DiffViewer-KR2GhX72.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-CN8xsiGC.js → ModelPreview-Bo6giTPm.js} +1 -1
- package/dist/client/assets/ModelPreview-Bo6giTPm.js.br +0 -0
- package/dist/client/assets/ModelPreview-Bo6giTPm.js.gz +0 -0
- package/dist/client/assets/RightSidebar-rROjRuPY.js +1678 -0
- package/dist/client/assets/RightSidebar-rROjRuPY.js.br +0 -0
- package/dist/client/assets/RightSidebar-rROjRuPY.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-DkdoJ2ZH.js → abnfDiagram-VCTEODGH-m85vHRkF.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-m85vHRkF.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-m85vHRkF.js.gz +0 -0
- package/dist/client/assets/app-Djvvt1Mi.js +34 -0
- package/dist/client/assets/app-Djvvt1Mi.js.br +0 -0
- package/dist/client/assets/app-Djvvt1Mi.js.gz +0 -0
- package/dist/client/assets/{arc-ctzz5PcK.js → arc-DmZ3EVhj.js} +1 -1
- package/dist/client/assets/arc-DmZ3EVhj.js.br +0 -0
- package/dist/client/assets/arc-DmZ3EVhj.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-DNLcYfGQ.js → architectureDiagram-5GKGNRK7-C8yGaNGM.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C8yGaNGM.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C8yGaNGM.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-Coye1ou5.js → blockDiagram-I7D4REHJ-CZhh__cR.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-CZhh__cR.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CZhh__cR.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-DTa8jyPp.js → c4Diagram-7LVT6UL2-py7O2OOO.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-py7O2OOO.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-py7O2OOO.js.gz +0 -0
- package/dist/client/assets/channel-BnKOV34C.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-BpDyjNfs.js → chunk-2Q5K7J3B-SXGoQcYW.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-DgwdqXKl.js → chunk-5VM5RSS4-1ZVyPSGv.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DWf2gZ96.js → chunk-F27PBJKO-DkIqnjYj.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-DkIqnjYj.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DkIqnjYj.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-l42o_E2c.js → chunk-IMKFNOWR-CLS_EByq.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-CLS_EByq.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CLS_EByq.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-8CkEf05l.js → chunk-JWPE2WC7-8LtK4UYW.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-BKKOfbdg.js → chunk-POPQ4Y6H-D1PhOQEv.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-RORrf2aM.js → chunk-SVP7TREG-cVWIfR_4.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-cVWIfR_4.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-cVWIfR_4.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-hPqafEtC.js → chunk-TICWLB2K-BiO8AIZ7.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-BiO8AIZ7.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BiO8AIZ7.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-CrvjJwms.js → chunk-XXDRQBXY-CwP0rAj8.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-CZ5ElqWa.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CZ5ElqWa.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CwRSiQLa.js → cose-bilkent-JH36ORCC-Dv9GK27H.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Dv9GK27H.js.br +0 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CwRSiQLa.js.gz → cose-bilkent-JH36ORCC-Dv9GK27H.js.gz} +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-CXnppZ8I.js → cynefin-OW5HDTMX-DDI6luT6.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-DDI6luT6.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DDI6luT6.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BSDrHSIA.js → cynefinDiagram-5FMLGOSQ-DQpaBQcE.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DQpaBQcE.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DQpaBQcE.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-d-5j4vkq.js → dagre-GXQ25YYZ-CZ-1rzmK.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-CZ-1rzmK.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CZ-1rzmK.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-DizMBpGW.js → diagram-S7CK7UJ4-DeYvGv1c.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-DeYvGv1c.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DeYvGv1c.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-CNQK26rH.js → diagram-UQ7AKVKN-BaYQC1t6.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-BaYQC1t6.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BaYQC1t6.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-Dua-2_46.js → diagram-VSXAHHWV-B517BnK0.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-B517BnK0.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-B517BnK0.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-CIq-4729.js → diagram-VX7I27RA-DyjVlDcv.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-DyjVlDcv.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DyjVlDcv.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-BukQ0u-x.js → diagram-Z3DM3KII-Cy9JXHOz.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-Cy9JXHOz.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-Cy9JXHOz.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-D2zRkRBI.js → ebnfDiagram-PWID7BFC-xvEH045b.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-xvEH045b.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-xvEH045b.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DbX6A3ls.js → erDiagram-RLTQ6QDP-BTJDgwKq.js} +1 -1
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DbX6A3ls.js.br → erDiagram-RLTQ6QDP-BTJDgwKq.js.br} +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BTJDgwKq.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-YECDWA2G.js → flowDiagram-HODETNUW-6pq1SF-S.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-6pq1SF-S.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-6pq1SF-S.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-D3EJdUov.js → ganttDiagram-EL5Y4UJY-DS_l5v9m.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DS_l5v9m.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DS_l5v9m.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-D3IZpZS7.js → gestureArbiter-FtwbRpdp.js} +1 -1
- package/dist/client/assets/gestureArbiter-FtwbRpdp.js.br +0 -0
- package/dist/client/assets/gestureArbiter-FtwbRpdp.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-tr_HKoQK.js → gitGraphDiagram-WWUBYQGX-DEGFo3aa.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DEGFo3aa.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DEGFo3aa.js.gz +0 -0
- package/dist/client/assets/{index-C4Ny0NDQ.css → index-PWu1VEN_.css} +1 -1
- package/dist/client/assets/index-PWu1VEN_.css.br +0 -0
- package/dist/client/assets/index-PWu1VEN_.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-xBV63YYg.js → infoDiagram-27XIBGKW-CFVSyJdD.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BrYSZu1R.js → ishikawaDiagram-5VMMS53U-_YX0U_wu.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-_YX0U_wu.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-_YX0U_wu.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-C9Ft--aA.js → journeyDiagram-3NMN7TZE-DCujswss.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DCujswss.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DCujswss.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-FCxqSu11.js → kanban-definition-UXKFOSKX-DC6lqIBi.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-DC6lqIBi.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-DC6lqIBi.js.gz +0 -0
- package/dist/client/assets/{layout-BqjIyPqO.js → layout-D_hlL0MC.js} +1 -1
- package/dist/client/assets/layout-D_hlL0MC.js.br +0 -0
- package/dist/client/assets/layout-D_hlL0MC.js.gz +0 -0
- package/dist/client/assets/{linear-CEAGQX5N.js → linear-BxpjJ2yt.js} +1 -1
- package/dist/client/assets/linear-BxpjJ2yt.js.br +0 -0
- package/dist/client/assets/linear-BxpjJ2yt.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-DvAY78t_.js → mermaid.core-BRLYB1Uj.js} +5 -5
- package/dist/client/assets/mermaid.core-BRLYB1Uj.js.br +0 -0
- package/dist/client/assets/mermaid.core-BRLYB1Uj.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-D7_qIQ9H.js → mindmap-definition-YA3MSWOX-BF2f-Z54.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BF2f-Z54.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BF2f-Z54.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-Dksfdvk3.js → passwordBootstrap-hj2Lcgoj.js} +1 -1
- package/dist/client/assets/passwordBootstrap-hj2Lcgoj.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-hj2Lcgoj.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-DeTIBKGC.js → pegDiagram-XKGWAZYB-FxHPBhCy.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-FxHPBhCy.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-FxHPBhCy.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-z5IcoVwx.js → pieDiagram-E7YTZNPT-DqAcArGH.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-DqAcArGH.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DqAcArGH.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B-zCZvES.js → quadrantDiagram-AXDQQJYC-31lk30-Y.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B-zCZvES.js.br → quadrantDiagram-AXDQQJYC-31lk30-Y.js.br} +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-31lk30-Y.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-DbpeB7P2.js → railroadDiagram-O6MQD6OU-C-Jt17Wl.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-C-Jt17Wl.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-C-Jt17Wl.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-p56g9xFb.js → referenceLongPress-BPFSnCDQ.js} +1 -1
- package/dist/client/assets/referenceLongPress-BPFSnCDQ.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BPFSnCDQ.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-UhJYMJ_U.js → requirementDiagram-BXWQKSXE-DdrbsuCJ.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DdrbsuCJ.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DdrbsuCJ.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-xKsuFR17.js → sankeyDiagram-P5KCCOFB-CZjs9KFo.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZjs9KFo.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZjs9KFo.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-DrjBl65C.js → sequenceDiagram-WJ2MYXX4-CkD1f6v4.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CkD1f6v4.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CkD1f6v4.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-BIlGJ9tm.js → sizeCapture-INFHLROL-BMuKV0gL.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-BMuKV0gL.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BMuKV0gL.js.gz +0 -0
- package/dist/client/assets/{sparkles-B-uEi0Pa.js → sparkles-DuRvUCWG.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-C3k9XvA6.js → stateDiagram-D77RDMKH-B1lLo2WA.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-B1lLo2WA.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-B1lLo2WA.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-F5MbngH8.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-BN-x1rXp.js → swimlanes-42K2YHIH-CfC5CNjA.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-CfC5CNjA.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-CfC5CNjA.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BqbZwNvL.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-Dcedjtic.js → timeline-definition-24CTP7MA-0mM-Skdo.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-0mM-Skdo.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-0mM-Skdo.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-BSNAwyll.js → triangle-alert-wyGdnPOg.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-DAIk-5Rt.js → vennDiagram-4TSXK5OY-BYsJo_hU.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-BYsJo_hU.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BYsJo_hU.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-3iXOTpAg.js → wardleyDiagram-VM6X3IG4-ree3u-pI.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-ree3u-pI.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-ree3u-pI.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-OD1a_9Qg.js → xychartDiagram-S5SC5T6Z-DDono4Wc.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DDono4Wc.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DDono4Wc.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-downloads.js +40 -0
- package/dist/client/sw-downloads.js.br +0 -0
- package/dist/client/sw-downloads.js.gz +0 -0
- package/dist/client/sw-encrypted-background.js +25 -0
- package/dist/client/sw-encrypted-background.js.br +0 -0
- package/dist/client/sw-encrypted-background.js.gz +0 -0
- package/dist/client/sw-notifications.js +11 -43
- package/dist/client/sw-notifications.js.br +0 -0
- package/dist/client/sw-notifications.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/collaborationCli.js +10 -3
- package/dist/server/agent/collaborationDeliveryWorker.js +154 -0
- package/dist/server/agent/collaborationRoutes.js +15 -4
- package/dist/server/agent/collaborationRouting.js +94 -0
- package/dist/server/agent/collaborationStore.js +6 -1
- package/dist/server/agent/collaborationTmuxDelivery.js +10 -0
- package/dist/server/agent/sessionBindingRecovery.js +80 -0
- package/dist/server/cli.js +9 -5
- package/dist/server/entry.js +32 -13
- package/dist/server/federation/desktopTargets.js +64 -0
- package/dist/server/federation/directRoutes.js +27 -19
- package/dist/server/federation/requestContext.js +7 -1
- package/dist/server/federation/routeAccess.js +14 -7
- package/dist/server/federation/runtime.js +5 -3
- package/dist/server/federation/trustedCa.js +55 -0
- package/dist/server/notifications/pushService.js +3 -0
- package/dist/server/routes/notifications.js +4 -0
- package/dist/server/routes/terminal.js +235 -78
- package/dist/server/utils/publicSecurity.js +1 -1
- package/package.json +2 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-CAEtVL8Z.js +0 -150
- package/dist/client/assets/App-CAEtVL8Z.js.br +0 -0
- package/dist/client/assets/App-CAEtVL8Z.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BNWqAnqc.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BNWqAnqc.js.gz +0 -0
- package/dist/client/assets/DagPlayground-IlTGwr-I.js.br +0 -0
- package/dist/client/assets/DagPlayground-IlTGwr-I.js.gz +0 -0
- package/dist/client/assets/DiffLab-CwhNpJvo.js.br +0 -0
- package/dist/client/assets/DiffLab-CwhNpJvo.js.gz +0 -0
- package/dist/client/assets/DiffReview-DyhINv0F.js.br +0 -0
- package/dist/client/assets/DiffReview-DyhINv0F.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-D9Kt5VwB.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-D9Kt5VwB.js.gz +0 -0
- package/dist/client/assets/DiffViewer-3zz2PI-8.js.br +0 -0
- package/dist/client/assets/DiffViewer-3zz2PI-8.js.gz +0 -0
- package/dist/client/assets/ModelPreview-CN8xsiGC.js.br +0 -0
- package/dist/client/assets/ModelPreview-CN8xsiGC.js.gz +0 -0
- package/dist/client/assets/RightSidebar-DQk1cwKH.js +0 -79
- package/dist/client/assets/RightSidebar-DQk1cwKH.js.br +0 -0
- package/dist/client/assets/RightSidebar-DQk1cwKH.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DkdoJ2ZH.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DkdoJ2ZH.js.gz +0 -0
- package/dist/client/assets/app-B42CoF1r.js +0 -34
- package/dist/client/assets/app-B42CoF1r.js.br +0 -0
- package/dist/client/assets/app-B42CoF1r.js.gz +0 -0
- package/dist/client/assets/arc-ctzz5PcK.js.br +0 -0
- package/dist/client/assets/arc-ctzz5PcK.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DNLcYfGQ.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DNLcYfGQ.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Coye1ou5.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Coye1ou5.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DTa8jyPp.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DTa8jyPp.js.gz +0 -0
- package/dist/client/assets/channel-CzvSDv06.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-DWf2gZ96.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DWf2gZ96.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-l42o_E2c.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-l42o_E2c.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-RORrf2aM.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-RORrf2aM.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-hPqafEtC.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-hPqafEtC.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-CDknJfE0.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CDknJfE0.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CwRSiQLa.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CXnppZ8I.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CXnppZ8I.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BSDrHSIA.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BSDrHSIA.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-d-5j4vkq.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-d-5j4vkq.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DizMBpGW.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DizMBpGW.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CNQK26rH.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CNQK26rH.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Dua-2_46.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Dua-2_46.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CIq-4729.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CIq-4729.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BukQ0u-x.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BukQ0u-x.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D2zRkRBI.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D2zRkRBI.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DbX6A3ls.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-YECDWA2G.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-YECDWA2G.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-D3EJdUov.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-D3EJdUov.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-D3IZpZS7.js.br +0 -0
- package/dist/client/assets/gestureArbiter-D3IZpZS7.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tr_HKoQK.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tr_HKoQK.js.gz +0 -0
- package/dist/client/assets/index-C4Ny0NDQ.css.br +0 -0
- package/dist/client/assets/index-C4Ny0NDQ.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BrYSZu1R.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BrYSZu1R.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-C9Ft--aA.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-C9Ft--aA.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-FCxqSu11.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-FCxqSu11.js.gz +0 -0
- package/dist/client/assets/layout-BqjIyPqO.js.br +0 -0
- package/dist/client/assets/layout-BqjIyPqO.js.gz +0 -0
- package/dist/client/assets/linear-CEAGQX5N.js.br +0 -0
- package/dist/client/assets/linear-CEAGQX5N.js.gz +0 -0
- package/dist/client/assets/mermaid.core-DvAY78t_.js.br +0 -0
- package/dist/client/assets/mermaid.core-DvAY78t_.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D7_qIQ9H.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D7_qIQ9H.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-Dksfdvk3.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-Dksfdvk3.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DeTIBKGC.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DeTIBKGC.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-z5IcoVwx.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-z5IcoVwx.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B-zCZvES.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DbpeB7P2.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DbpeB7P2.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-p56g9xFb.js.br +0 -0
- package/dist/client/assets/referenceLongPress-p56g9xFb.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-UhJYMJ_U.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-UhJYMJ_U.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-xKsuFR17.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-xKsuFR17.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DrjBl65C.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DrjBl65C.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BIlGJ9tm.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BIlGJ9tm.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-C3k9XvA6.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-C3k9XvA6.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-B7GnCu2o.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BN-x1rXp.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BN-x1rXp.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BH_gm34Y.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-Dcedjtic.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-Dcedjtic.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DAIk-5Rt.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DAIk-5Rt.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-3iXOTpAg.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-3iXOTpAg.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-OD1a_9Qg.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-OD1a_9Qg.js.gz +0 -0
package/dist/server/entry.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { setPushTargetPeerId } from './notifications/pushService.js';
|
|
2
|
+
import { desktopDirectTargets } from './federation/desktopTargets.js';
|
|
1
3
|
import { createOpenAccessRouter } from './federation/openAccess.js';
|
|
2
4
|
import { createPasswordLoginRouter } from './federation/passwordLoginRoutes.js';
|
|
3
5
|
import { apiAccessGate, isTrustedLocalRequest } from './utils/apiAccess.js';
|
|
@@ -404,27 +406,42 @@ export function startServer(options = {}) {
|
|
|
404
406
|
const { server, scheme } = createServerForApp(app, options);
|
|
405
407
|
let entrySubjectAllowed = (_subjectId) => false;
|
|
406
408
|
let routeInvitations;
|
|
407
|
-
|
|
409
|
+
let federationServiceId = '';
|
|
410
|
+
const desktopTargets = () => desktopDirectTargets(path.join(homedir(), '.termdock', 'desktop.json'), federationServiceId);
|
|
411
|
+
const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true, desktopTargets);
|
|
408
412
|
const relayRouter = new RelayRouter({
|
|
409
413
|
authenticate: context => context,
|
|
410
414
|
allowRegister: (principal, serviceId) => routeAccess.allowRegister(principal, serviceId),
|
|
411
415
|
allowRoute: (principal, serviceId) => routeAccess.allowRoute(principal, serviceId),
|
|
412
416
|
});
|
|
413
417
|
const dynamicDirectTargets = new Map();
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
418
|
+
const refreshDirectTargets = () => {
|
|
419
|
+
const desired = new Map(routeAccess.configuredDirectTargets().filter(target => target.serviceId !== federationServiceId).map(target => [target.serviceId, target]));
|
|
420
|
+
for (const [id, registered] of dynamicDirectTargets) {
|
|
421
|
+
const target = desired.get(id);
|
|
422
|
+
if (!target || registered.signature !== JSON.stringify([target.url, target.caPath, target.caFingerprint256])) {
|
|
423
|
+
registered.close();
|
|
424
|
+
dynamicDirectTargets.delete(id);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
for (const [id, target] of desired)
|
|
428
|
+
if (!dynamicDirectTargets.has(id)) {
|
|
429
|
+
try {
|
|
430
|
+
dynamicDirectTargets.set(id, { signature: JSON.stringify([target.url, target.caPath, target.caFingerprint256]), close: attachRegisteredDirectTargets(relayRouter, [target], path.join(homedir(), '.termdock', 'federation')) });
|
|
431
|
+
}
|
|
432
|
+
catch { /* A malformed or unavailable local route remains unusable. */ }
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
refreshDirectTargets();
|
|
436
|
+
const desktopRoutesTimer = setInterval(refreshDirectTargets, 2000);
|
|
437
|
+
desktopRoutesTimer.unref();
|
|
438
|
+
server.once('close', () => { clearInterval(desktopRoutesTimer); for (const target of dynamicDirectTargets.values())
|
|
439
|
+
target.close(); });
|
|
423
440
|
server.once('close', () => relayRouter.close());
|
|
424
441
|
const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
|
|
425
442
|
terminal: handleTerminalWebSocket, control: handleControlWebSocket,
|
|
426
443
|
}, {
|
|
427
|
-
listRouteTargets: () => routeAccess.configuredTargets().map(target => ({ ...target, available: relayRouter.hasRoute(target.serviceId) })),
|
|
444
|
+
listRouteTargets: () => { refreshDirectTargets(); return routeAccess.configuredTargets().map(target => ({ ...target, available: relayRouter.hasRoute(target.serviceId) })); },
|
|
428
445
|
listRouteAccess: () => routeInvitations?.list() || [],
|
|
429
446
|
grantRouteAccess: async (issuerId, targetServiceId, subjectId, url) => {
|
|
430
447
|
if (!routeInvitations)
|
|
@@ -438,8 +455,7 @@ export function startServer(options = {}) {
|
|
|
438
455
|
if (!runtime.store.authorize({ subjectId: issuerId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed)
|
|
439
456
|
throw new Error('AUTHORIZATION_DENIED');
|
|
440
457
|
routeAccess.addDirectTarget(target);
|
|
441
|
-
|
|
442
|
-
dynamicDirectTargets.set(targetServiceId, attachRegisteredDirectTargets(relayRouter, [target]));
|
|
458
|
+
refreshDirectTargets();
|
|
443
459
|
}
|
|
444
460
|
return routeInvitations.grant(issuerId, targetServiceId, subjectId);
|
|
445
461
|
},
|
|
@@ -458,6 +474,9 @@ export function startServer(options = {}) {
|
|
|
458
474
|
},
|
|
459
475
|
});
|
|
460
476
|
void federation.then(runtime => {
|
|
477
|
+
federationServiceId = runtime.serviceId;
|
|
478
|
+
setPushTargetPeerId(runtime.serviceId);
|
|
479
|
+
refreshDirectTargets();
|
|
461
480
|
app.locals.passwordRuntime = runtime;
|
|
462
481
|
entrySubjectAllowed = subjectId => runtime.store.authorize({ subjectId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed;
|
|
463
482
|
routeInvitations = new RouteInvitationStore({
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
const peer = (value) => typeof value === 'string' && /^[a-zA-Z0-9_.:-]{1,160}$/.test(value);
|
|
3
|
+
const fingerprint = (value) => typeof value === 'string' && /^(?:[\da-f]{2}:){31}[\da-f]{2}$/i.test(value);
|
|
4
|
+
function origin(value) {
|
|
5
|
+
if (typeof value !== 'string' || value.length > 2048)
|
|
6
|
+
return;
|
|
7
|
+
try {
|
|
8
|
+
const url = new URL(value);
|
|
9
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || url.pathname !== '/')
|
|
10
|
+
return;
|
|
11
|
+
return url.origin;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Read only this OS user's Desktop directory. The local service must itself
|
|
18
|
+
* have been pinned there before its administrator can discover other entries.
|
|
19
|
+
* This supplies addresses, never the Desktop client's device authorization.
|
|
20
|
+
*/
|
|
21
|
+
export function desktopDirectTargets(file, localServiceId) {
|
|
22
|
+
if (!localServiceId)
|
|
23
|
+
return [];
|
|
24
|
+
try {
|
|
25
|
+
if (statSync(file).size > 1024 * 1024)
|
|
26
|
+
return [];
|
|
27
|
+
const data = JSON.parse(readFileSync(file, 'utf8'));
|
|
28
|
+
if (data?.version !== 1 || !Array.isArray(data.connections) || data.connections.length > 256)
|
|
29
|
+
return [];
|
|
30
|
+
const removed = new Set(Array.isArray(data.removedServiceKeys) ? data.removedServiceKeys : []);
|
|
31
|
+
const connections = data.connections.filter((item) => item && ![item.id, item.targetPeerId, item.serviceOrigin || item.url].some(key => removed.has(key)));
|
|
32
|
+
if (!connections.some((item) => item.targetPeerId === localServiceId))
|
|
33
|
+
return [];
|
|
34
|
+
const trusts = new Map();
|
|
35
|
+
if (Array.isArray(data.trustedCertificateAuthorities))
|
|
36
|
+
for (const trust of data.trustedCertificateAuthorities.slice(0, 256)) {
|
|
37
|
+
const url = origin(trust?.origin);
|
|
38
|
+
if (url && fingerprint(trust.fingerprint256))
|
|
39
|
+
trusts.set(url, trust.fingerprint256.toUpperCase());
|
|
40
|
+
}
|
|
41
|
+
const targets = new Map();
|
|
42
|
+
for (const item of connections) {
|
|
43
|
+
if (!peer(item.targetPeerId) || item.targetPeerId === localServiceId)
|
|
44
|
+
continue;
|
|
45
|
+
const url = origin(item.serviceOrigin || item.url);
|
|
46
|
+
if (!url)
|
|
47
|
+
continue;
|
|
48
|
+
// An entry-only bookmark does not supply a direct address for its target.
|
|
49
|
+
if ((!item.serviceOrigin && item.entryServiceId && item.entryServiceId !== item.targetPeerId)
|
|
50
|
+
|| (Array.isArray(item.routes) && item.routes.some((route) => route && route.targetPeerId !== item.targetPeerId && origin(route.url) === url)))
|
|
51
|
+
continue;
|
|
52
|
+
const caFingerprint256 = trusts.get(url);
|
|
53
|
+
targets.set(item.targetPeerId, { serviceId: item.targetPeerId, url,
|
|
54
|
+
...(typeof item.label === 'string' && item.label.trim() ? { label: item.label.trim().slice(0, 120) } : {}),
|
|
55
|
+
...(caFingerprint256 ? { caFingerprint256 } : {}) });
|
|
56
|
+
if (targets.size >= 64)
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
return [...targets.values()];
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { readPinnedCertificateAuthority } from './trustedCa.js';
|
|
1
2
|
import { createIdentity, secureConnection } from './secureProtocol.js';
|
|
2
3
|
import { socketDuplex } from './socketDuplex.js';
|
|
3
4
|
import { readFileSync } from 'node:fs';
|
|
4
5
|
import { resolve } from 'node:path';
|
|
5
6
|
import WebSocket from 'ws';
|
|
6
7
|
function endpoint(config) {
|
|
7
|
-
if (!config || typeof config !== 'object' || Object.keys(config).some(key => !['serviceId', 'url', 'caPath'].includes(key)) || !/^[a-zA-Z0-9_.:-]{1,160}$/.test(config.serviceId) || typeof config.url !== 'string' || (config.caPath !== undefined && typeof config.caPath !== 'string'))
|
|
8
|
+
if (!config || typeof config !== 'object' || Object.keys(config).some(key => !['serviceId', 'url', 'caPath', 'caFingerprint256', 'label'].includes(key)) || !/^[a-zA-Z0-9_.:-]{1,160}$/.test(config.serviceId) || typeof config.url !== 'string' || (config.caPath !== undefined && typeof config.caPath !== 'string'))
|
|
8
9
|
throw new Error('Invalid direct target configuration');
|
|
10
|
+
if ((config.caFingerprint256 !== undefined && (typeof config.caFingerprint256 !== 'string' || !/^(?:[\da-f]{2}:){31}[\da-f]{2}$/i.test(config.caFingerprint256))) || (config.caPath && config.caFingerprint256) || (config.label !== undefined && (typeof config.label !== 'string' || config.label.length > 120)))
|
|
11
|
+
throw new Error('Invalid direct target metadata');
|
|
9
12
|
const url = new URL(config.url);
|
|
10
13
|
if (!['https:', 'wss:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || !['/', '/api/federation/secure'].includes(url.pathname))
|
|
11
14
|
throw new Error('Direct targets require a verified HTTPS/WSS origin');
|
|
@@ -24,7 +27,7 @@ export function attachRegisteredDirectTargets(router, configs, baseDir = process
|
|
|
24
27
|
if (ids.has(config.serviceId))
|
|
25
28
|
throw new Error('Duplicate direct target');
|
|
26
29
|
ids.add(config.serviceId);
|
|
27
|
-
return { serviceId: config.serviceId, url, ca: config.caPath ? readFileSync(resolve(baseDir, config.caPath)) : undefined };
|
|
30
|
+
return { serviceId: config.serviceId, url, caFingerprint256: config.caFingerprint256, ca: config.caPath ? readFileSync(resolve(baseDir, config.caPath)) : undefined };
|
|
28
31
|
});
|
|
29
32
|
let stopped = false;
|
|
30
33
|
const sockets = new Set();
|
|
@@ -34,22 +37,25 @@ export function attachRegisteredDirectTargets(router, configs, baseDir = process
|
|
|
34
37
|
ws.terminate(); };
|
|
35
38
|
try {
|
|
36
39
|
for (const target of targets)
|
|
37
|
-
cleanups.push(router.registerDirect(target.serviceId,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
failed
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
cleanups.push(router.registerDirect(target.serviceId, async () => {
|
|
41
|
+
const ca = target.caFingerprint256 ? await readPinnedCertificateAuthority(target.url.replace(/^wss:/, 'https:'), target.caFingerprint256) : target.ca;
|
|
42
|
+
return new Promise((accept, reject) => {
|
|
43
|
+
if (stopped) {
|
|
44
|
+
reject(new Error('Direct target stopped'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const ws = new WebSocket(target.url, { ca, rejectUnauthorized: true, followRedirects: false, handshakeTimeout: 10000, maxPayload: 256 * 1024 });
|
|
48
|
+
sockets.add(ws);
|
|
49
|
+
ws.once('close', () => sockets.delete(ws));
|
|
50
|
+
const failed = () => { reject(new Error('Direct target unavailable')); ws.terminate(); };
|
|
51
|
+
ws.once('error', failed);
|
|
52
|
+
ws.once('close', failed);
|
|
53
|
+
ws.once('open', () => { ws.off('error', failed); ws.off('close', failed); ws.on('error', () => ws.terminate()); if (stopped)
|
|
54
|
+
failed();
|
|
55
|
+
else
|
|
56
|
+
accept(ws); });
|
|
57
|
+
});
|
|
58
|
+
}));
|
|
53
59
|
}
|
|
54
60
|
catch (error) {
|
|
55
61
|
cleanup();
|
|
@@ -60,7 +66,9 @@ export function attachRegisteredDirectTargets(router, configs, baseDir = process
|
|
|
60
66
|
/** An administrator enabling a new backup first proves the target's pinned
|
|
61
67
|
* identity; HTTPS validation remains enabled throughout. */
|
|
62
68
|
export async function verifyDirectTarget(config) {
|
|
63
|
-
const
|
|
69
|
+
const url = endpoint(config);
|
|
70
|
+
const ca = config.caFingerprint256 ? await readPinnedCertificateAuthority(url.replace(/^wss:/, 'https:'), config.caFingerprint256) : config.caPath ? readFileSync(config.caPath) : undefined;
|
|
71
|
+
const ws = new WebSocket(url, { ca, rejectUnauthorized: true, followRedirects: false, handshakeTimeout: 5000, maxPayload: 256 * 1024 });
|
|
64
72
|
const duplex = socketDuplex(ws);
|
|
65
73
|
try {
|
|
66
74
|
await new Promise((resolve, reject) => { ws.once('open', resolve); ws.once('error', reject); ws.once('close', () => reject(new Error('Target closed'))); });
|
|
@@ -1,4 +1,10 @@
|
|
|
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
|
+
export function markEncryptedRequest(request, subjectId) {
|
|
5
|
+
verified.add(request);
|
|
6
|
+
if (subjectId)
|
|
7
|
+
subjects.set(request, subjectId);
|
|
8
|
+
}
|
|
4
9
|
export function isEncryptedRequest(request) { return verified.has(request); }
|
|
10
|
+
export function encryptedRequestSubject(request) { return subjects.get(request); }
|
|
@@ -8,11 +8,13 @@ export class RouteAccess {
|
|
|
8
8
|
filePath;
|
|
9
9
|
now;
|
|
10
10
|
canRouteSubject;
|
|
11
|
+
additionalTargets;
|
|
11
12
|
tickets = new Map();
|
|
12
|
-
constructor(filePath, now = Date.now, canRouteSubject = () => false) {
|
|
13
|
+
constructor(filePath, now = Date.now, canRouteSubject = () => false, additionalTargets = () => []) {
|
|
13
14
|
this.filePath = filePath;
|
|
14
15
|
this.now = now;
|
|
15
16
|
this.canRouteSubject = canRouteSubject;
|
|
17
|
+
this.additionalTargets = additionalTargets;
|
|
16
18
|
}
|
|
17
19
|
registry() {
|
|
18
20
|
const empty = { relays: [], directTargets: [] };
|
|
@@ -32,8 +34,8 @@ export class RouteAccess {
|
|
|
32
34
|
}
|
|
33
35
|
const targetIds = new Set();
|
|
34
36
|
for (const target of directTargets) {
|
|
35
|
-
if (!target || typeof target !== 'object' || Array.isArray(target) || Object.keys(target).some(key => !['serviceId', 'url', 'caPath'].includes(key))
|
|
36
|
-
|| !validId(target.serviceId) || targetIds.has(target.serviceId) || typeof target.url !== 'string' || (target.caPath !== undefined && typeof target.caPath !== 'string'))
|
|
37
|
+
if (!target || typeof target !== 'object' || Array.isArray(target) || Object.keys(target).some(key => !['serviceId', 'url', 'caPath', 'label'].includes(key))
|
|
38
|
+
|| (target.label !== undefined && (typeof target.label !== 'string' || target.label.length > 120)) || !validId(target.serviceId) || targetIds.has(target.serviceId) || typeof target.url !== 'string' || (target.caPath !== undefined && typeof target.caPath !== 'string'))
|
|
37
39
|
return empty;
|
|
38
40
|
const url = new URL(target.url);
|
|
39
41
|
if (!['https:', 'wss:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || !['/', '/api/federation/secure'].includes(url.pathname))
|
|
@@ -90,7 +92,7 @@ export class RouteAccess {
|
|
|
90
92
|
if (!validId(serviceId))
|
|
91
93
|
return false;
|
|
92
94
|
const registry = this.registry();
|
|
93
|
-
return registry.relays.some(relay => relay.targets.includes(serviceId)) || registry.directTargets.some(target => target.serviceId === serviceId);
|
|
95
|
+
return registry.relays.some(relay => relay.targets.includes(serviceId)) || registry.directTargets.some(target => target.serviceId === serviceId) || this.additionalTargets().some(target => target.serviceId === serviceId);
|
|
94
96
|
}
|
|
95
97
|
addDirectTarget(target) {
|
|
96
98
|
const url = new URL(target.url);
|
|
@@ -120,17 +122,22 @@ export class RouteAccess {
|
|
|
120
122
|
writeFileSync(temporary, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
121
123
|
renameSync(temporary, this.filePath);
|
|
122
124
|
}
|
|
123
|
-
configuredDirectTargets() {
|
|
125
|
+
configuredDirectTargets() {
|
|
126
|
+
const targets = new Map(this.additionalTargets().map(target => [target.serviceId, target]));
|
|
127
|
+
for (const target of this.registry().directTargets)
|
|
128
|
+
targets.set(target.serviceId, target);
|
|
129
|
+
return [...targets.values()];
|
|
130
|
+
}
|
|
124
131
|
configuredTargets() {
|
|
125
132
|
const registry = this.registry();
|
|
126
133
|
const targets = new Map();
|
|
127
134
|
for (const relay of registry.relays)
|
|
128
135
|
for (const serviceId of relay.targets)
|
|
129
136
|
targets.set(serviceId, { serviceId });
|
|
130
|
-
for (const target of
|
|
137
|
+
for (const target of this.configuredDirectTargets()) {
|
|
131
138
|
const url = new URL(target.url);
|
|
132
139
|
url.protocol = 'https:';
|
|
133
|
-
targets.set(target.serviceId, { serviceId: target.serviceId, url: url.origin });
|
|
140
|
+
targets.set(target.serviceId, { serviceId: target.serviceId, url: url.origin, ...(target.label ? { label: target.label } : {}) });
|
|
134
141
|
}
|
|
135
142
|
return [...targets.values()];
|
|
136
143
|
}
|
|
@@ -95,7 +95,9 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
97
|
delete request.headers['x-termdock-inner'];
|
|
98
|
-
|
|
98
|
+
const subject = request.headers['x-termdock-device'];
|
|
99
|
+
delete request.headers['x-termdock-device'];
|
|
100
|
+
markEncryptedRequest(request, typeof subject === 'string' ? subject : undefined);
|
|
99
101
|
app(request, response);
|
|
100
102
|
});
|
|
101
103
|
await new Promise((resolve, reject) => { internal.once('error', reject); internal.listen(0, '127.0.0.1', resolve); });
|
|
@@ -152,7 +154,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
152
154
|
};
|
|
153
155
|
try {
|
|
154
156
|
httpAllowed(operation.head);
|
|
155
|
-
const headers = new Headers({ 'x-termdock-inner': internalToken });
|
|
157
|
+
const headers = new Headers({ 'x-termdock-inner': internalToken, 'x-termdock-device': subjectId });
|
|
156
158
|
const supplied = operation.head.headers;
|
|
157
159
|
if (supplied && typeof supplied === 'object')
|
|
158
160
|
for (const [name, value] of Object.entries(supplied)) {
|
|
@@ -482,7 +484,7 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
482
484
|
send({ type: 'ws-ready', id: packet.id });
|
|
483
485
|
if (match)
|
|
484
486
|
handlers.terminal(logical, match[1], randomUUID(), {
|
|
485
|
-
sinceSeq: Math.max(0, Number(url.searchParams.get('since')) || 0), streamEpoch: url.searchParams.get('epoch') ?? undefined,
|
|
487
|
+
pushClientId: `device:${subjectId}`, sinceSeq: Math.max(0, Number(url.searchParams.get('since')) || 0), streamEpoch: url.searchParams.get('epoch') ?? undefined,
|
|
486
488
|
flowControl: url.searchParams.get('flow') === '2', outputActive: url.searchParams.get('active') !== '0',
|
|
487
489
|
independentTmux: full(subjectId) && url.searchParams.get('transport') === 'tmux-client',
|
|
488
490
|
}, allowed(subjectId, 'session.resize', match[1]) ? readTerminalHandshakeDimensions(url.searchParams) : undefined);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { X509Certificate } from 'node:crypto';
|
|
2
|
+
import { get } from 'node:https';
|
|
3
|
+
const cache = new Map();
|
|
4
|
+
/** Only the CA download is unauthenticated. Its exact SHA-256 fingerprint was
|
|
5
|
+
* already approved locally for this origin; the subsequent WSS connection must
|
|
6
|
+
* still pass normal TLS chain, hostname and expiry checks using this CA.
|
|
7
|
+
*/
|
|
8
|
+
export function readPinnedCertificateAuthority(origin, fingerprint) {
|
|
9
|
+
const url = new URL('/onboarding/ca.crt', origin);
|
|
10
|
+
if (url.protocol !== 'https:' || url.username || url.password || !/^(?:[\da-f]{2}:){31}[\da-f]{2}$/i.test(fingerprint))
|
|
11
|
+
return Promise.reject(new Error('Invalid pinned certificate authority'));
|
|
12
|
+
const key = `${url.origin}:${fingerprint.toUpperCase()}`;
|
|
13
|
+
const cached = cache.get(key);
|
|
14
|
+
if (cached && cached.expires > Date.now())
|
|
15
|
+
return cached.value;
|
|
16
|
+
const value = new Promise((resolve, reject) => {
|
|
17
|
+
const request = get(url, { agent: false, rejectUnauthorized: false, signal: AbortSignal.timeout(5000) }, response => {
|
|
18
|
+
if (response.statusCode !== 200) {
|
|
19
|
+
response.resume();
|
|
20
|
+
reject(new Error('Pinned certificate authority unavailable'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const chunks = [];
|
|
24
|
+
let size = 0;
|
|
25
|
+
response.on('data', (chunk) => {
|
|
26
|
+
size += chunk.length;
|
|
27
|
+
if (size > 256 * 1024) {
|
|
28
|
+
request.destroy(new Error('Certificate authority is too large'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
chunks.push(chunk);
|
|
32
|
+
});
|
|
33
|
+
response.on('error', reject);
|
|
34
|
+
response.on('end', () => {
|
|
35
|
+
try {
|
|
36
|
+
const pem = Buffer.concat(chunks), certificate = new X509Certificate(pem);
|
|
37
|
+
if (!certificate.ca || certificate.fingerprint256 !== fingerprint.toUpperCase()
|
|
38
|
+
|| Date.now() < Date.parse(certificate.validFrom) || Date.now() > Date.parse(certificate.validTo))
|
|
39
|
+
throw new Error('Certificate authority no longer matches Desktop trust');
|
|
40
|
+
resolve(pem);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
reject(error);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
request.on('error', reject);
|
|
48
|
+
});
|
|
49
|
+
if (cache.size >= 64)
|
|
50
|
+
cache.delete(cache.keys().next().value);
|
|
51
|
+
cache.set(key, { expires: Date.now() + 120_000, value });
|
|
52
|
+
void value.catch(() => { if (cache.get(key)?.value === value)
|
|
53
|
+
cache.delete(key); });
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
let pushTargetPeerId;
|
|
2
|
+
export function setPushTargetPeerId(value) { pushTargetPeerId = value; }
|
|
1
3
|
import fs from 'node:fs';
|
|
2
4
|
import os from 'node:os';
|
|
3
5
|
import path from 'node:path';
|
|
@@ -124,6 +126,7 @@ async function sendPayload(options) {
|
|
|
124
126
|
};
|
|
125
127
|
await webpush.sendNotification(subscription, JSON.stringify({
|
|
126
128
|
...options.build(entry.locale),
|
|
129
|
+
targetPeerId: pushTargetPeerId,
|
|
127
130
|
alertStyle: entry.alertStyle,
|
|
128
131
|
locale: entry.locale,
|
|
129
132
|
}), {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
+
import { encryptedRequestSubject } from '../federation/requestContext.js';
|
|
2
3
|
import { getPushSubscription, getVapidPublicKey, removePushSubscription, updatePushPreferences, upsertPushSubscription, } from '../notifications/pushService.js';
|
|
3
4
|
const router = Router();
|
|
4
5
|
function clientIdFromRequest(req) {
|
|
6
|
+
const subject = encryptedRequestSubject(req);
|
|
7
|
+
if (subject)
|
|
8
|
+
return `device:${subject}`;
|
|
5
9
|
return typeof req.cookies?.['termdock-client'] === 'string' ? req.cookies['termdock-client'] : '';
|
|
6
10
|
}
|
|
7
11
|
function validAlertStyle(value) {
|