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.
Files changed (345) hide show
  1. package/.desktop-dist/main.js +1 -1
  2. package/dist/client/assets/App-DBds0RoW.js +150 -0
  3. package/dist/client/assets/App-DBds0RoW.js.br +0 -0
  4. package/dist/client/assets/App-DBds0RoW.js.gz +0 -0
  5. package/dist/client/assets/{ChangeWalkthroughPanel-BNWqAnqc.js → ChangeWalkthroughPanel-DonkfMV2.js} +2 -2
  6. package/dist/client/assets/ChangeWalkthroughPanel-DonkfMV2.js.br +0 -0
  7. package/dist/client/assets/ChangeWalkthroughPanel-DonkfMV2.js.gz +0 -0
  8. package/dist/client/assets/{DagPlayground-IlTGwr-I.js → DagPlayground-CYL8vhaU.js} +1 -1
  9. package/dist/client/assets/DagPlayground-CYL8vhaU.js.br +0 -0
  10. package/dist/client/assets/DagPlayground-CYL8vhaU.js.gz +0 -0
  11. package/dist/client/assets/{DiffLab-CwhNpJvo.js → DiffLab-COUjebir.js} +1 -1
  12. package/dist/client/assets/DiffLab-COUjebir.js.br +0 -0
  13. package/dist/client/assets/DiffLab-COUjebir.js.gz +0 -0
  14. package/dist/client/assets/{DiffReview-DyhINv0F.js → DiffReview-DqiRXomQ.js} +1 -1
  15. package/dist/client/assets/DiffReview-DqiRXomQ.js.br +0 -0
  16. package/dist/client/assets/DiffReview-DqiRXomQ.js.gz +0 -0
  17. package/dist/client/assets/{DiffReviewLab-D9Kt5VwB.js → DiffReviewLab-Aqib8Qr6.js} +1 -1
  18. package/dist/client/assets/DiffReviewLab-Aqib8Qr6.js.br +0 -0
  19. package/dist/client/assets/DiffReviewLab-Aqib8Qr6.js.gz +0 -0
  20. package/dist/client/assets/{DiffViewer-3zz2PI-8.js → DiffViewer-KR2GhX72.js} +1 -1
  21. package/dist/client/assets/DiffViewer-KR2GhX72.js.br +0 -0
  22. package/dist/client/assets/DiffViewer-KR2GhX72.js.gz +0 -0
  23. package/dist/client/assets/{ModelPreview-CN8xsiGC.js → ModelPreview-Bo6giTPm.js} +1 -1
  24. package/dist/client/assets/ModelPreview-Bo6giTPm.js.br +0 -0
  25. package/dist/client/assets/ModelPreview-Bo6giTPm.js.gz +0 -0
  26. package/dist/client/assets/RightSidebar-rROjRuPY.js +1678 -0
  27. package/dist/client/assets/RightSidebar-rROjRuPY.js.br +0 -0
  28. package/dist/client/assets/RightSidebar-rROjRuPY.js.gz +0 -0
  29. package/dist/client/assets/{abnfDiagram-VCTEODGH-DkdoJ2ZH.js → abnfDiagram-VCTEODGH-m85vHRkF.js} +1 -1
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-m85vHRkF.js.br +0 -0
  31. package/dist/client/assets/abnfDiagram-VCTEODGH-m85vHRkF.js.gz +0 -0
  32. package/dist/client/assets/app-Djvvt1Mi.js +34 -0
  33. package/dist/client/assets/app-Djvvt1Mi.js.br +0 -0
  34. package/dist/client/assets/app-Djvvt1Mi.js.gz +0 -0
  35. package/dist/client/assets/{arc-ctzz5PcK.js → arc-DmZ3EVhj.js} +1 -1
  36. package/dist/client/assets/arc-DmZ3EVhj.js.br +0 -0
  37. package/dist/client/assets/arc-DmZ3EVhj.js.gz +0 -0
  38. package/dist/client/assets/{architectureDiagram-5GKGNRK7-DNLcYfGQ.js → architectureDiagram-5GKGNRK7-C8yGaNGM.js} +1 -1
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-C8yGaNGM.js.br +0 -0
  40. package/dist/client/assets/architectureDiagram-5GKGNRK7-C8yGaNGM.js.gz +0 -0
  41. package/dist/client/assets/{blockDiagram-I7D4REHJ-Coye1ou5.js → blockDiagram-I7D4REHJ-CZhh__cR.js} +1 -1
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-CZhh__cR.js.br +0 -0
  43. package/dist/client/assets/blockDiagram-I7D4REHJ-CZhh__cR.js.gz +0 -0
  44. package/dist/client/assets/{c4Diagram-7LVT6UL2-DTa8jyPp.js → c4Diagram-7LVT6UL2-py7O2OOO.js} +1 -1
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-py7O2OOO.js.br +0 -0
  46. package/dist/client/assets/c4Diagram-7LVT6UL2-py7O2OOO.js.gz +0 -0
  47. package/dist/client/assets/channel-BnKOV34C.js +1 -0
  48. package/dist/client/assets/{chunk-2Q5K7J3B-BpDyjNfs.js → chunk-2Q5K7J3B-SXGoQcYW.js} +1 -1
  49. package/dist/client/assets/{chunk-5VM5RSS4-DgwdqXKl.js → chunk-5VM5RSS4-1ZVyPSGv.js} +1 -1
  50. package/dist/client/assets/{chunk-F27PBJKO-DWf2gZ96.js → chunk-F27PBJKO-DkIqnjYj.js} +1 -1
  51. package/dist/client/assets/chunk-F27PBJKO-DkIqnjYj.js.br +0 -0
  52. package/dist/client/assets/chunk-F27PBJKO-DkIqnjYj.js.gz +0 -0
  53. package/dist/client/assets/{chunk-IMKFNOWR-l42o_E2c.js → chunk-IMKFNOWR-CLS_EByq.js} +1 -1
  54. package/dist/client/assets/chunk-IMKFNOWR-CLS_EByq.js.br +0 -0
  55. package/dist/client/assets/chunk-IMKFNOWR-CLS_EByq.js.gz +0 -0
  56. package/dist/client/assets/{chunk-JWPE2WC7-8CkEf05l.js → chunk-JWPE2WC7-8LtK4UYW.js} +1 -1
  57. package/dist/client/assets/{chunk-POPQ4Y6H-BKKOfbdg.js → chunk-POPQ4Y6H-D1PhOQEv.js} +1 -1
  58. package/dist/client/assets/{chunk-SVP7TREG-RORrf2aM.js → chunk-SVP7TREG-cVWIfR_4.js} +1 -1
  59. package/dist/client/assets/chunk-SVP7TREG-cVWIfR_4.js.br +0 -0
  60. package/dist/client/assets/chunk-SVP7TREG-cVWIfR_4.js.gz +0 -0
  61. package/dist/client/assets/{chunk-TICWLB2K-hPqafEtC.js → chunk-TICWLB2K-BiO8AIZ7.js} +1 -1
  62. package/dist/client/assets/chunk-TICWLB2K-BiO8AIZ7.js.br +0 -0
  63. package/dist/client/assets/chunk-TICWLB2K-BiO8AIZ7.js.gz +0 -0
  64. package/dist/client/assets/{chunk-XXDRQBXY-CrvjJwms.js → chunk-XXDRQBXY-CwP0rAj8.js} +1 -1
  65. package/dist/client/assets/classDiagram-ZZMXUADV-CZ5ElqWa.js +1 -0
  66. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CZ5ElqWa.js +1 -0
  67. package/dist/client/assets/{cose-bilkent-JH36ORCC-CwRSiQLa.js → cose-bilkent-JH36ORCC-Dv9GK27H.js} +1 -1
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-Dv9GK27H.js.br +0 -0
  69. package/dist/client/assets/{cose-bilkent-JH36ORCC-CwRSiQLa.js.gz → cose-bilkent-JH36ORCC-Dv9GK27H.js.gz} +0 -0
  70. package/dist/client/assets/{cynefin-OW5HDTMX-CXnppZ8I.js → cynefin-OW5HDTMX-DDI6luT6.js} +1 -1
  71. package/dist/client/assets/cynefin-OW5HDTMX-DDI6luT6.js.br +0 -0
  72. package/dist/client/assets/cynefin-OW5HDTMX-DDI6luT6.js.gz +0 -0
  73. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BSDrHSIA.js → cynefinDiagram-5FMLGOSQ-DQpaBQcE.js} +1 -1
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DQpaBQcE.js.br +0 -0
  75. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DQpaBQcE.js.gz +0 -0
  76. package/dist/client/assets/{dagre-GXQ25YYZ-d-5j4vkq.js → dagre-GXQ25YYZ-CZ-1rzmK.js} +1 -1
  77. package/dist/client/assets/dagre-GXQ25YYZ-CZ-1rzmK.js.br +0 -0
  78. package/dist/client/assets/dagre-GXQ25YYZ-CZ-1rzmK.js.gz +0 -0
  79. package/dist/client/assets/{diagram-S7CK7UJ4-DizMBpGW.js → diagram-S7CK7UJ4-DeYvGv1c.js} +1 -1
  80. package/dist/client/assets/diagram-S7CK7UJ4-DeYvGv1c.js.br +0 -0
  81. package/dist/client/assets/diagram-S7CK7UJ4-DeYvGv1c.js.gz +0 -0
  82. package/dist/client/assets/{diagram-UQ7AKVKN-CNQK26rH.js → diagram-UQ7AKVKN-BaYQC1t6.js} +1 -1
  83. package/dist/client/assets/diagram-UQ7AKVKN-BaYQC1t6.js.br +0 -0
  84. package/dist/client/assets/diagram-UQ7AKVKN-BaYQC1t6.js.gz +0 -0
  85. package/dist/client/assets/{diagram-VSXAHHWV-Dua-2_46.js → diagram-VSXAHHWV-B517BnK0.js} +1 -1
  86. package/dist/client/assets/diagram-VSXAHHWV-B517BnK0.js.br +0 -0
  87. package/dist/client/assets/diagram-VSXAHHWV-B517BnK0.js.gz +0 -0
  88. package/dist/client/assets/{diagram-VX7I27RA-CIq-4729.js → diagram-VX7I27RA-DyjVlDcv.js} +1 -1
  89. package/dist/client/assets/diagram-VX7I27RA-DyjVlDcv.js.br +0 -0
  90. package/dist/client/assets/diagram-VX7I27RA-DyjVlDcv.js.gz +0 -0
  91. package/dist/client/assets/{diagram-Z3DM3KII-BukQ0u-x.js → diagram-Z3DM3KII-Cy9JXHOz.js} +1 -1
  92. package/dist/client/assets/diagram-Z3DM3KII-Cy9JXHOz.js.br +0 -0
  93. package/dist/client/assets/diagram-Z3DM3KII-Cy9JXHOz.js.gz +0 -0
  94. package/dist/client/assets/{ebnfDiagram-PWID7BFC-D2zRkRBI.js → ebnfDiagram-PWID7BFC-xvEH045b.js} +1 -1
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-xvEH045b.js.br +0 -0
  96. package/dist/client/assets/ebnfDiagram-PWID7BFC-xvEH045b.js.gz +0 -0
  97. package/dist/client/assets/{erDiagram-RLTQ6QDP-DbX6A3ls.js → erDiagram-RLTQ6QDP-BTJDgwKq.js} +1 -1
  98. package/dist/client/assets/{erDiagram-RLTQ6QDP-DbX6A3ls.js.br → erDiagram-RLTQ6QDP-BTJDgwKq.js.br} +0 -0
  99. package/dist/client/assets/erDiagram-RLTQ6QDP-BTJDgwKq.js.gz +0 -0
  100. package/dist/client/assets/{flowDiagram-HODETNUW-YECDWA2G.js → flowDiagram-HODETNUW-6pq1SF-S.js} +1 -1
  101. package/dist/client/assets/flowDiagram-HODETNUW-6pq1SF-S.js.br +0 -0
  102. package/dist/client/assets/flowDiagram-HODETNUW-6pq1SF-S.js.gz +0 -0
  103. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-D3EJdUov.js → ganttDiagram-EL5Y4UJY-DS_l5v9m.js} +1 -1
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DS_l5v9m.js.br +0 -0
  105. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DS_l5v9m.js.gz +0 -0
  106. package/dist/client/assets/{gestureArbiter-D3IZpZS7.js → gestureArbiter-FtwbRpdp.js} +1 -1
  107. package/dist/client/assets/gestureArbiter-FtwbRpdp.js.br +0 -0
  108. package/dist/client/assets/gestureArbiter-FtwbRpdp.js.gz +0 -0
  109. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-tr_HKoQK.js → gitGraphDiagram-WWUBYQGX-DEGFo3aa.js} +1 -1
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DEGFo3aa.js.br +0 -0
  111. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DEGFo3aa.js.gz +0 -0
  112. package/dist/client/assets/{index-C4Ny0NDQ.css → index-PWu1VEN_.css} +1 -1
  113. package/dist/client/assets/index-PWu1VEN_.css.br +0 -0
  114. package/dist/client/assets/index-PWu1VEN_.css.gz +0 -0
  115. package/dist/client/assets/{infoDiagram-27XIBGKW-xBV63YYg.js → infoDiagram-27XIBGKW-CFVSyJdD.js} +1 -1
  116. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BrYSZu1R.js → ishikawaDiagram-5VMMS53U-_YX0U_wu.js} +1 -1
  117. package/dist/client/assets/ishikawaDiagram-5VMMS53U-_YX0U_wu.js.br +0 -0
  118. package/dist/client/assets/ishikawaDiagram-5VMMS53U-_YX0U_wu.js.gz +0 -0
  119. package/dist/client/assets/{journeyDiagram-3NMN7TZE-C9Ft--aA.js → journeyDiagram-3NMN7TZE-DCujswss.js} +1 -1
  120. package/dist/client/assets/journeyDiagram-3NMN7TZE-DCujswss.js.br +0 -0
  121. package/dist/client/assets/journeyDiagram-3NMN7TZE-DCujswss.js.gz +0 -0
  122. package/dist/client/assets/{kanban-definition-UXKFOSKX-FCxqSu11.js → kanban-definition-UXKFOSKX-DC6lqIBi.js} +1 -1
  123. package/dist/client/assets/kanban-definition-UXKFOSKX-DC6lqIBi.js.br +0 -0
  124. package/dist/client/assets/kanban-definition-UXKFOSKX-DC6lqIBi.js.gz +0 -0
  125. package/dist/client/assets/{layout-BqjIyPqO.js → layout-D_hlL0MC.js} +1 -1
  126. package/dist/client/assets/layout-D_hlL0MC.js.br +0 -0
  127. package/dist/client/assets/layout-D_hlL0MC.js.gz +0 -0
  128. package/dist/client/assets/{linear-CEAGQX5N.js → linear-BxpjJ2yt.js} +1 -1
  129. package/dist/client/assets/linear-BxpjJ2yt.js.br +0 -0
  130. package/dist/client/assets/linear-BxpjJ2yt.js.gz +0 -0
  131. package/dist/client/assets/{mermaid.core-DvAY78t_.js → mermaid.core-BRLYB1Uj.js} +5 -5
  132. package/dist/client/assets/mermaid.core-BRLYB1Uj.js.br +0 -0
  133. package/dist/client/assets/mermaid.core-BRLYB1Uj.js.gz +0 -0
  134. package/dist/client/assets/{mindmap-definition-YA3MSWOX-D7_qIQ9H.js → mindmap-definition-YA3MSWOX-BF2f-Z54.js} +1 -1
  135. package/dist/client/assets/mindmap-definition-YA3MSWOX-BF2f-Z54.js.br +0 -0
  136. package/dist/client/assets/mindmap-definition-YA3MSWOX-BF2f-Z54.js.gz +0 -0
  137. package/dist/client/assets/{passwordBootstrap-Dksfdvk3.js → passwordBootstrap-hj2Lcgoj.js} +1 -1
  138. package/dist/client/assets/passwordBootstrap-hj2Lcgoj.js.br +0 -0
  139. package/dist/client/assets/passwordBootstrap-hj2Lcgoj.js.gz +0 -0
  140. package/dist/client/assets/{pegDiagram-XKGWAZYB-DeTIBKGC.js → pegDiagram-XKGWAZYB-FxHPBhCy.js} +1 -1
  141. package/dist/client/assets/pegDiagram-XKGWAZYB-FxHPBhCy.js.br +0 -0
  142. package/dist/client/assets/pegDiagram-XKGWAZYB-FxHPBhCy.js.gz +0 -0
  143. package/dist/client/assets/{pieDiagram-E7YTZNPT-z5IcoVwx.js → pieDiagram-E7YTZNPT-DqAcArGH.js} +1 -1
  144. package/dist/client/assets/pieDiagram-E7YTZNPT-DqAcArGH.js.br +0 -0
  145. package/dist/client/assets/pieDiagram-E7YTZNPT-DqAcArGH.js.gz +0 -0
  146. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B-zCZvES.js → quadrantDiagram-AXDQQJYC-31lk30-Y.js} +1 -1
  147. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B-zCZvES.js.br → quadrantDiagram-AXDQQJYC-31lk30-Y.js.br} +0 -0
  148. package/dist/client/assets/quadrantDiagram-AXDQQJYC-31lk30-Y.js.gz +0 -0
  149. package/dist/client/assets/{railroadDiagram-O6MQD6OU-DbpeB7P2.js → railroadDiagram-O6MQD6OU-C-Jt17Wl.js} +1 -1
  150. package/dist/client/assets/railroadDiagram-O6MQD6OU-C-Jt17Wl.js.br +0 -0
  151. package/dist/client/assets/railroadDiagram-O6MQD6OU-C-Jt17Wl.js.gz +0 -0
  152. package/dist/client/assets/{referenceLongPress-p56g9xFb.js → referenceLongPress-BPFSnCDQ.js} +1 -1
  153. package/dist/client/assets/referenceLongPress-BPFSnCDQ.js.br +0 -0
  154. package/dist/client/assets/referenceLongPress-BPFSnCDQ.js.gz +0 -0
  155. package/dist/client/assets/{requirementDiagram-BXWQKSXE-UhJYMJ_U.js → requirementDiagram-BXWQKSXE-DdrbsuCJ.js} +1 -1
  156. package/dist/client/assets/requirementDiagram-BXWQKSXE-DdrbsuCJ.js.br +0 -0
  157. package/dist/client/assets/requirementDiagram-BXWQKSXE-DdrbsuCJ.js.gz +0 -0
  158. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-xKsuFR17.js → sankeyDiagram-P5KCCOFB-CZjs9KFo.js} +1 -1
  159. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZjs9KFo.js.br +0 -0
  160. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZjs9KFo.js.gz +0 -0
  161. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-DrjBl65C.js → sequenceDiagram-WJ2MYXX4-CkD1f6v4.js} +1 -1
  162. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CkD1f6v4.js.br +0 -0
  163. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CkD1f6v4.js.gz +0 -0
  164. package/dist/client/assets/{sizeCapture-INFHLROL-BIlGJ9tm.js → sizeCapture-INFHLROL-BMuKV0gL.js} +1 -1
  165. package/dist/client/assets/sizeCapture-INFHLROL-BMuKV0gL.js.br +0 -0
  166. package/dist/client/assets/sizeCapture-INFHLROL-BMuKV0gL.js.gz +0 -0
  167. package/dist/client/assets/{sparkles-B-uEi0Pa.js → sparkles-DuRvUCWG.js} +1 -1
  168. package/dist/client/assets/{stateDiagram-D77RDMKH-C3k9XvA6.js → stateDiagram-D77RDMKH-B1lLo2WA.js} +1 -1
  169. package/dist/client/assets/stateDiagram-D77RDMKH-B1lLo2WA.js.br +0 -0
  170. package/dist/client/assets/stateDiagram-D77RDMKH-B1lLo2WA.js.gz +0 -0
  171. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-F5MbngH8.js +1 -0
  172. package/dist/client/assets/{swimlanes-42K2YHIH-BN-x1rXp.js → swimlanes-42K2YHIH-CfC5CNjA.js} +1 -1
  173. package/dist/client/assets/swimlanes-42K2YHIH-CfC5CNjA.js.br +0 -0
  174. package/dist/client/assets/swimlanes-42K2YHIH-CfC5CNjA.js.gz +0 -0
  175. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BqbZwNvL.js +8 -0
  176. package/dist/client/assets/{timeline-definition-24CTP7MA-Dcedjtic.js → timeline-definition-24CTP7MA-0mM-Skdo.js} +1 -1
  177. package/dist/client/assets/timeline-definition-24CTP7MA-0mM-Skdo.js.br +0 -0
  178. package/dist/client/assets/timeline-definition-24CTP7MA-0mM-Skdo.js.gz +0 -0
  179. package/dist/client/assets/{triangle-alert-BSNAwyll.js → triangle-alert-wyGdnPOg.js} +1 -1
  180. package/dist/client/assets/{vennDiagram-4TSXK5OY-DAIk-5Rt.js → vennDiagram-4TSXK5OY-BYsJo_hU.js} +1 -1
  181. package/dist/client/assets/vennDiagram-4TSXK5OY-BYsJo_hU.js.br +0 -0
  182. package/dist/client/assets/vennDiagram-4TSXK5OY-BYsJo_hU.js.gz +0 -0
  183. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-3iXOTpAg.js → wardleyDiagram-VM6X3IG4-ree3u-pI.js} +1 -1
  184. package/dist/client/assets/wardleyDiagram-VM6X3IG4-ree3u-pI.js.br +0 -0
  185. package/dist/client/assets/wardleyDiagram-VM6X3IG4-ree3u-pI.js.gz +0 -0
  186. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-OD1a_9Qg.js → xychartDiagram-S5SC5T6Z-DDono4Wc.js} +1 -1
  187. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DDono4Wc.js.br +0 -0
  188. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DDono4Wc.js.gz +0 -0
  189. package/dist/client/index.html +2 -2
  190. package/dist/client/index.html.br +0 -0
  191. package/dist/client/index.html.gz +0 -0
  192. package/dist/client/sw-downloads.js +40 -0
  193. package/dist/client/sw-downloads.js.br +0 -0
  194. package/dist/client/sw-downloads.js.gz +0 -0
  195. package/dist/client/sw-encrypted-background.js +25 -0
  196. package/dist/client/sw-encrypted-background.js.br +0 -0
  197. package/dist/client/sw-encrypted-background.js.gz +0 -0
  198. package/dist/client/sw-notifications.js +11 -43
  199. package/dist/client/sw-notifications.js.br +0 -0
  200. package/dist/client/sw-notifications.js.gz +0 -0
  201. package/dist/client/sw.js +1 -1
  202. package/dist/client/sw.js.br +0 -0
  203. package/dist/client/sw.js.gz +0 -0
  204. package/dist/server/agent/collaborationCli.js +10 -3
  205. package/dist/server/agent/collaborationDeliveryWorker.js +154 -0
  206. package/dist/server/agent/collaborationRoutes.js +15 -4
  207. package/dist/server/agent/collaborationRouting.js +94 -0
  208. package/dist/server/agent/collaborationStore.js +6 -1
  209. package/dist/server/agent/collaborationTmuxDelivery.js +10 -0
  210. package/dist/server/agent/sessionBindingRecovery.js +80 -0
  211. package/dist/server/cli.js +9 -5
  212. package/dist/server/entry.js +32 -13
  213. package/dist/server/federation/desktopTargets.js +64 -0
  214. package/dist/server/federation/directRoutes.js +27 -19
  215. package/dist/server/federation/requestContext.js +7 -1
  216. package/dist/server/federation/routeAccess.js +14 -7
  217. package/dist/server/federation/runtime.js +5 -3
  218. package/dist/server/federation/trustedCa.js +55 -0
  219. package/dist/server/notifications/pushService.js +3 -0
  220. package/dist/server/routes/notifications.js +4 -0
  221. package/dist/server/routes/terminal.js +235 -78
  222. package/dist/server/utils/publicSecurity.js +1 -1
  223. package/package.json +2 -1
  224. package/runtime-manifest.json +3 -3
  225. package/dist/client/assets/App-CAEtVL8Z.js +0 -150
  226. package/dist/client/assets/App-CAEtVL8Z.js.br +0 -0
  227. package/dist/client/assets/App-CAEtVL8Z.js.gz +0 -0
  228. package/dist/client/assets/ChangeWalkthroughPanel-BNWqAnqc.js.br +0 -0
  229. package/dist/client/assets/ChangeWalkthroughPanel-BNWqAnqc.js.gz +0 -0
  230. package/dist/client/assets/DagPlayground-IlTGwr-I.js.br +0 -0
  231. package/dist/client/assets/DagPlayground-IlTGwr-I.js.gz +0 -0
  232. package/dist/client/assets/DiffLab-CwhNpJvo.js.br +0 -0
  233. package/dist/client/assets/DiffLab-CwhNpJvo.js.gz +0 -0
  234. package/dist/client/assets/DiffReview-DyhINv0F.js.br +0 -0
  235. package/dist/client/assets/DiffReview-DyhINv0F.js.gz +0 -0
  236. package/dist/client/assets/DiffReviewLab-D9Kt5VwB.js.br +0 -0
  237. package/dist/client/assets/DiffReviewLab-D9Kt5VwB.js.gz +0 -0
  238. package/dist/client/assets/DiffViewer-3zz2PI-8.js.br +0 -0
  239. package/dist/client/assets/DiffViewer-3zz2PI-8.js.gz +0 -0
  240. package/dist/client/assets/ModelPreview-CN8xsiGC.js.br +0 -0
  241. package/dist/client/assets/ModelPreview-CN8xsiGC.js.gz +0 -0
  242. package/dist/client/assets/RightSidebar-DQk1cwKH.js +0 -79
  243. package/dist/client/assets/RightSidebar-DQk1cwKH.js.br +0 -0
  244. package/dist/client/assets/RightSidebar-DQk1cwKH.js.gz +0 -0
  245. package/dist/client/assets/abnfDiagram-VCTEODGH-DkdoJ2ZH.js.br +0 -0
  246. package/dist/client/assets/abnfDiagram-VCTEODGH-DkdoJ2ZH.js.gz +0 -0
  247. package/dist/client/assets/app-B42CoF1r.js +0 -34
  248. package/dist/client/assets/app-B42CoF1r.js.br +0 -0
  249. package/dist/client/assets/app-B42CoF1r.js.gz +0 -0
  250. package/dist/client/assets/arc-ctzz5PcK.js.br +0 -0
  251. package/dist/client/assets/arc-ctzz5PcK.js.gz +0 -0
  252. package/dist/client/assets/architectureDiagram-5GKGNRK7-DNLcYfGQ.js.br +0 -0
  253. package/dist/client/assets/architectureDiagram-5GKGNRK7-DNLcYfGQ.js.gz +0 -0
  254. package/dist/client/assets/blockDiagram-I7D4REHJ-Coye1ou5.js.br +0 -0
  255. package/dist/client/assets/blockDiagram-I7D4REHJ-Coye1ou5.js.gz +0 -0
  256. package/dist/client/assets/c4Diagram-7LVT6UL2-DTa8jyPp.js.br +0 -0
  257. package/dist/client/assets/c4Diagram-7LVT6UL2-DTa8jyPp.js.gz +0 -0
  258. package/dist/client/assets/channel-CzvSDv06.js +0 -1
  259. package/dist/client/assets/chunk-F27PBJKO-DWf2gZ96.js.br +0 -0
  260. package/dist/client/assets/chunk-F27PBJKO-DWf2gZ96.js.gz +0 -0
  261. package/dist/client/assets/chunk-IMKFNOWR-l42o_E2c.js.br +0 -0
  262. package/dist/client/assets/chunk-IMKFNOWR-l42o_E2c.js.gz +0 -0
  263. package/dist/client/assets/chunk-SVP7TREG-RORrf2aM.js.br +0 -0
  264. package/dist/client/assets/chunk-SVP7TREG-RORrf2aM.js.gz +0 -0
  265. package/dist/client/assets/chunk-TICWLB2K-hPqafEtC.js.br +0 -0
  266. package/dist/client/assets/chunk-TICWLB2K-hPqafEtC.js.gz +0 -0
  267. package/dist/client/assets/classDiagram-ZZMXUADV-CDknJfE0.js +0 -1
  268. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CDknJfE0.js +0 -1
  269. package/dist/client/assets/cose-bilkent-JH36ORCC-CwRSiQLa.js.br +0 -0
  270. package/dist/client/assets/cynefin-OW5HDTMX-CXnppZ8I.js.br +0 -0
  271. package/dist/client/assets/cynefin-OW5HDTMX-CXnppZ8I.js.gz +0 -0
  272. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BSDrHSIA.js.br +0 -0
  273. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BSDrHSIA.js.gz +0 -0
  274. package/dist/client/assets/dagre-GXQ25YYZ-d-5j4vkq.js.br +0 -0
  275. package/dist/client/assets/dagre-GXQ25YYZ-d-5j4vkq.js.gz +0 -0
  276. package/dist/client/assets/diagram-S7CK7UJ4-DizMBpGW.js.br +0 -0
  277. package/dist/client/assets/diagram-S7CK7UJ4-DizMBpGW.js.gz +0 -0
  278. package/dist/client/assets/diagram-UQ7AKVKN-CNQK26rH.js.br +0 -0
  279. package/dist/client/assets/diagram-UQ7AKVKN-CNQK26rH.js.gz +0 -0
  280. package/dist/client/assets/diagram-VSXAHHWV-Dua-2_46.js.br +0 -0
  281. package/dist/client/assets/diagram-VSXAHHWV-Dua-2_46.js.gz +0 -0
  282. package/dist/client/assets/diagram-VX7I27RA-CIq-4729.js.br +0 -0
  283. package/dist/client/assets/diagram-VX7I27RA-CIq-4729.js.gz +0 -0
  284. package/dist/client/assets/diagram-Z3DM3KII-BukQ0u-x.js.br +0 -0
  285. package/dist/client/assets/diagram-Z3DM3KII-BukQ0u-x.js.gz +0 -0
  286. package/dist/client/assets/ebnfDiagram-PWID7BFC-D2zRkRBI.js.br +0 -0
  287. package/dist/client/assets/ebnfDiagram-PWID7BFC-D2zRkRBI.js.gz +0 -0
  288. package/dist/client/assets/erDiagram-RLTQ6QDP-DbX6A3ls.js.gz +0 -0
  289. package/dist/client/assets/flowDiagram-HODETNUW-YECDWA2G.js.br +0 -0
  290. package/dist/client/assets/flowDiagram-HODETNUW-YECDWA2G.js.gz +0 -0
  291. package/dist/client/assets/ganttDiagram-EL5Y4UJY-D3EJdUov.js.br +0 -0
  292. package/dist/client/assets/ganttDiagram-EL5Y4UJY-D3EJdUov.js.gz +0 -0
  293. package/dist/client/assets/gestureArbiter-D3IZpZS7.js.br +0 -0
  294. package/dist/client/assets/gestureArbiter-D3IZpZS7.js.gz +0 -0
  295. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tr_HKoQK.js.br +0 -0
  296. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tr_HKoQK.js.gz +0 -0
  297. package/dist/client/assets/index-C4Ny0NDQ.css.br +0 -0
  298. package/dist/client/assets/index-C4Ny0NDQ.css.gz +0 -0
  299. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BrYSZu1R.js.br +0 -0
  300. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BrYSZu1R.js.gz +0 -0
  301. package/dist/client/assets/journeyDiagram-3NMN7TZE-C9Ft--aA.js.br +0 -0
  302. package/dist/client/assets/journeyDiagram-3NMN7TZE-C9Ft--aA.js.gz +0 -0
  303. package/dist/client/assets/kanban-definition-UXKFOSKX-FCxqSu11.js.br +0 -0
  304. package/dist/client/assets/kanban-definition-UXKFOSKX-FCxqSu11.js.gz +0 -0
  305. package/dist/client/assets/layout-BqjIyPqO.js.br +0 -0
  306. package/dist/client/assets/layout-BqjIyPqO.js.gz +0 -0
  307. package/dist/client/assets/linear-CEAGQX5N.js.br +0 -0
  308. package/dist/client/assets/linear-CEAGQX5N.js.gz +0 -0
  309. package/dist/client/assets/mermaid.core-DvAY78t_.js.br +0 -0
  310. package/dist/client/assets/mermaid.core-DvAY78t_.js.gz +0 -0
  311. package/dist/client/assets/mindmap-definition-YA3MSWOX-D7_qIQ9H.js.br +0 -0
  312. package/dist/client/assets/mindmap-definition-YA3MSWOX-D7_qIQ9H.js.gz +0 -0
  313. package/dist/client/assets/passwordBootstrap-Dksfdvk3.js.br +0 -0
  314. package/dist/client/assets/passwordBootstrap-Dksfdvk3.js.gz +0 -0
  315. package/dist/client/assets/pegDiagram-XKGWAZYB-DeTIBKGC.js.br +0 -0
  316. package/dist/client/assets/pegDiagram-XKGWAZYB-DeTIBKGC.js.gz +0 -0
  317. package/dist/client/assets/pieDiagram-E7YTZNPT-z5IcoVwx.js.br +0 -0
  318. package/dist/client/assets/pieDiagram-E7YTZNPT-z5IcoVwx.js.gz +0 -0
  319. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B-zCZvES.js.gz +0 -0
  320. package/dist/client/assets/railroadDiagram-O6MQD6OU-DbpeB7P2.js.br +0 -0
  321. package/dist/client/assets/railroadDiagram-O6MQD6OU-DbpeB7P2.js.gz +0 -0
  322. package/dist/client/assets/referenceLongPress-p56g9xFb.js.br +0 -0
  323. package/dist/client/assets/referenceLongPress-p56g9xFb.js.gz +0 -0
  324. package/dist/client/assets/requirementDiagram-BXWQKSXE-UhJYMJ_U.js.br +0 -0
  325. package/dist/client/assets/requirementDiagram-BXWQKSXE-UhJYMJ_U.js.gz +0 -0
  326. package/dist/client/assets/sankeyDiagram-P5KCCOFB-xKsuFR17.js.br +0 -0
  327. package/dist/client/assets/sankeyDiagram-P5KCCOFB-xKsuFR17.js.gz +0 -0
  328. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DrjBl65C.js.br +0 -0
  329. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DrjBl65C.js.gz +0 -0
  330. package/dist/client/assets/sizeCapture-INFHLROL-BIlGJ9tm.js.br +0 -0
  331. package/dist/client/assets/sizeCapture-INFHLROL-BIlGJ9tm.js.gz +0 -0
  332. package/dist/client/assets/stateDiagram-D77RDMKH-C3k9XvA6.js.br +0 -0
  333. package/dist/client/assets/stateDiagram-D77RDMKH-C3k9XvA6.js.gz +0 -0
  334. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-B7GnCu2o.js +0 -1
  335. package/dist/client/assets/swimlanes-42K2YHIH-BN-x1rXp.js.br +0 -0
  336. package/dist/client/assets/swimlanes-42K2YHIH-BN-x1rXp.js.gz +0 -0
  337. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BH_gm34Y.js +0 -8
  338. package/dist/client/assets/timeline-definition-24CTP7MA-Dcedjtic.js.br +0 -0
  339. package/dist/client/assets/timeline-definition-24CTP7MA-Dcedjtic.js.gz +0 -0
  340. package/dist/client/assets/vennDiagram-4TSXK5OY-DAIk-5Rt.js.br +0 -0
  341. package/dist/client/assets/vennDiagram-4TSXK5OY-DAIk-5Rt.js.gz +0 -0
  342. package/dist/client/assets/wardleyDiagram-VM6X3IG4-3iXOTpAg.js.br +0 -0
  343. package/dist/client/assets/wardleyDiagram-VM6X3IG4-3iXOTpAg.js.gz +0 -0
  344. package/dist/client/assets/xychartDiagram-S5SC5T6Z-OD1a_9Qg.js.br +0 -0
  345. package/dist/client/assets/xychartDiagram-S5SC5T6Z-OD1a_9Qg.js.gz +0 -0
@@ -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
- const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true);
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
- server.once('close', () => { for (const close of dynamicDirectTargets.values())
415
- close(); });
416
- try {
417
- const detachDirectTargets = attachRegisteredDirectTargets(relayRouter, routeAccess.configuredDirectTargets(), path.join(homedir(), '.termdock', 'federation'));
418
- server.once('close', detachDirectTargets);
419
- }
420
- catch {
421
- console.error('Configured direct routes unavailable; target access remains closed.');
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
- if (!dynamicDirectTargets.has(targetServiceId))
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, () => new Promise((accept, reject) => {
38
- if (stopped) {
39
- reject(new Error('Direct target stopped'));
40
- return;
41
- }
42
- const ws = new WebSocket(target.url, { ca: target.ca, rejectUnauthorized: true, followRedirects: false, handshakeTimeout: 10000, maxPayload: 256 * 1024 });
43
- sockets.add(ws);
44
- ws.once('close', () => sockets.delete(ws));
45
- const failed = () => { reject(new Error('Direct target unavailable')); ws.terminate(); };
46
- ws.once('error', failed);
47
- ws.once('close', failed);
48
- ws.once('open', () => { ws.off('error', failed); ws.off('close', failed); ws.on('error', () => ws.terminate()); if (stopped)
49
- failed();
50
- else
51
- accept(ws); });
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 ws = new WebSocket(endpoint(config), { rejectUnauthorized: true, followRedirects: false, handshakeTimeout: 5000, maxPayload: 256 * 1024 });
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
- export function markEncryptedRequest(request) { verified.add(request); }
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() { return this.registry().directTargets; }
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 registry.directTargets) {
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
- markEncryptedRequest(request);
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) {