termdock 1.4.239 → 1.4.241

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 (313) hide show
  1. package/dist/client/assets/{App-CmlGDKDj.js → App-Dk1zwrS6.js} +3 -3
  2. package/dist/client/assets/App-Dk1zwrS6.js.br +0 -0
  3. package/dist/client/assets/App-Dk1zwrS6.js.gz +0 -0
  4. package/dist/client/assets/{ChangeWalkthroughPanel-9whVVXTr.js → ChangeWalkthroughPanel-C8tldKLn.js} +1 -1
  5. package/dist/client/assets/ChangeWalkthroughPanel-C8tldKLn.js.br +0 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-C8tldKLn.js.gz +0 -0
  7. package/dist/client/assets/{DagPlayground-y80OTTQj.js → DagPlayground-_8xFwzoy.js} +1 -1
  8. package/dist/client/assets/DagPlayground-_8xFwzoy.js.br +0 -0
  9. package/dist/client/assets/DagPlayground-_8xFwzoy.js.gz +0 -0
  10. package/dist/client/assets/{DiffLab-BY2wbRtg.js → DiffLab-Bm27LDoY.js} +1 -1
  11. package/dist/client/assets/DiffLab-Bm27LDoY.js.br +0 -0
  12. package/dist/client/assets/DiffLab-Bm27LDoY.js.gz +0 -0
  13. package/dist/client/assets/{DiffReview-DLlDJbK1.js → DiffReview-B_gl5KEk.js} +1 -1
  14. package/dist/client/assets/DiffReview-B_gl5KEk.js.br +0 -0
  15. package/dist/client/assets/DiffReview-B_gl5KEk.js.gz +0 -0
  16. package/dist/client/assets/{DiffReviewLab-CEpa_B8I.js → DiffReviewLab-Dgu955Xn.js} +1 -1
  17. package/dist/client/assets/DiffReviewLab-Dgu955Xn.js.br +0 -0
  18. package/dist/client/assets/DiffReviewLab-Dgu955Xn.js.gz +0 -0
  19. package/dist/client/assets/{DiffViewer-GLkd1zMQ.js → DiffViewer-3tHakIDI.js} +1 -1
  20. package/dist/client/assets/DiffViewer-3tHakIDI.js.br +0 -0
  21. package/dist/client/assets/DiffViewer-3tHakIDI.js.gz +0 -0
  22. package/dist/client/assets/{ModelPreview-DB8tw45f.js → ModelPreview-D8rlVB76.js} +1 -1
  23. package/dist/client/assets/ModelPreview-D8rlVB76.js.br +0 -0
  24. package/dist/client/assets/ModelPreview-D8rlVB76.js.gz +0 -0
  25. package/dist/client/assets/{RightSidebar-aoJjOQGX.js → RightSidebar-Cd6fVuZ6.js} +4 -4
  26. package/dist/client/assets/RightSidebar-Cd6fVuZ6.js.br +0 -0
  27. package/dist/client/assets/RightSidebar-Cd6fVuZ6.js.gz +0 -0
  28. package/dist/client/assets/{abnfDiagram-VCTEODGH-RBGYciHa.js → abnfDiagram-VCTEODGH-Dt72PWoy.js} +1 -1
  29. package/dist/client/assets/abnfDiagram-VCTEODGH-Dt72PWoy.js.br +0 -0
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-Dt72PWoy.js.gz +0 -0
  31. package/dist/client/assets/{app-Ctrh4Bku.js → app-DuOQ5oY3.js} +9 -9
  32. package/dist/client/assets/app-DuOQ5oY3.js.br +0 -0
  33. package/dist/client/assets/app-DuOQ5oY3.js.gz +0 -0
  34. package/dist/client/assets/{arc-CzQsNCup.js → arc-CKvN_JkE.js} +1 -1
  35. package/dist/client/assets/arc-CKvN_JkE.js.br +0 -0
  36. package/dist/client/assets/arc-CKvN_JkE.js.gz +0 -0
  37. package/dist/client/assets/{architectureDiagram-5GKGNRK7-sDp6hSiy.js → architectureDiagram-5GKGNRK7-CX7Gjex9.js} +1 -1
  38. package/dist/client/assets/{architectureDiagram-5GKGNRK7-sDp6hSiy.js.br → architectureDiagram-5GKGNRK7-CX7Gjex9.js.br} +0 -0
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-CX7Gjex9.js.gz +0 -0
  40. package/dist/client/assets/{blockDiagram-I7D4REHJ-C-AHqZPo.js → blockDiagram-I7D4REHJ-BMaBqkw8.js} +1 -1
  41. package/dist/client/assets/blockDiagram-I7D4REHJ-BMaBqkw8.js.br +0 -0
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-BMaBqkw8.js.gz +0 -0
  43. package/dist/client/assets/{c4Diagram-7LVT6UL2-B8VGhLwN.js → c4Diagram-7LVT6UL2-Bm7U73rl.js} +1 -1
  44. package/dist/client/assets/c4Diagram-7LVT6UL2-Bm7U73rl.js.br +0 -0
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-Bm7U73rl.js.gz +0 -0
  46. package/dist/client/assets/channel-BukD64h5.js +1 -0
  47. package/dist/client/assets/{chunk-2Q5K7J3B-D56jCJTr.js → chunk-2Q5K7J3B-B1NuLwpJ.js} +1 -1
  48. package/dist/client/assets/{chunk-5VM5RSS4-cga3WKIM.js → chunk-5VM5RSS4-CjfAUVix.js} +1 -1
  49. package/dist/client/assets/{chunk-F27PBJKO-BNOR_7WA.js → chunk-F27PBJKO-KLmJjuc2.js} +1 -1
  50. package/dist/client/assets/chunk-F27PBJKO-KLmJjuc2.js.br +0 -0
  51. package/dist/client/assets/chunk-F27PBJKO-KLmJjuc2.js.gz +0 -0
  52. package/dist/client/assets/{chunk-IMKFNOWR-Dk12DKtC.js → chunk-IMKFNOWR-D1b50_xa.js} +1 -1
  53. package/dist/client/assets/chunk-IMKFNOWR-D1b50_xa.js.br +0 -0
  54. package/dist/client/assets/{chunk-IMKFNOWR-Dk12DKtC.js.gz → chunk-IMKFNOWR-D1b50_xa.js.gz} +0 -0
  55. package/dist/client/assets/{chunk-JWPE2WC7-CVqdLg0d.js → chunk-JWPE2WC7-CrjqhBX6.js} +1 -1
  56. package/dist/client/assets/{chunk-POPQ4Y6H-Y6gEzK15.js → chunk-POPQ4Y6H-Izugq_I1.js} +1 -1
  57. package/dist/client/assets/{chunk-SVP7TREG-BIJtdFBL.js → chunk-SVP7TREG-sL_467mK.js} +1 -1
  58. package/dist/client/assets/chunk-SVP7TREG-sL_467mK.js.br +0 -0
  59. package/dist/client/assets/chunk-SVP7TREG-sL_467mK.js.gz +0 -0
  60. package/dist/client/assets/{chunk-TICWLB2K-CS3jsxv7.js → chunk-TICWLB2K-BQWSilD2.js} +1 -1
  61. package/dist/client/assets/chunk-TICWLB2K-BQWSilD2.js.br +0 -0
  62. package/dist/client/assets/chunk-TICWLB2K-BQWSilD2.js.gz +0 -0
  63. package/dist/client/assets/{chunk-XXDRQBXY-BYgvxPrj.js → chunk-XXDRQBXY-C10F0akw.js} +1 -1
  64. package/dist/client/assets/classDiagram-ZZMXUADV-DA86AYYB.js +1 -0
  65. package/dist/client/assets/classDiagram-v2-VYDZK3BY-DA86AYYB.js +1 -0
  66. package/dist/client/assets/{cose-bilkent-JH36ORCC-9udJiW7h.js → cose-bilkent-JH36ORCC-CrPAUlRG.js} +1 -1
  67. package/dist/client/assets/cose-bilkent-JH36ORCC-CrPAUlRG.js.br +0 -0
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-CrPAUlRG.js.gz +0 -0
  69. package/dist/client/assets/{cynefin-OW5HDTMX-D-6q-6N2.js → cynefin-OW5HDTMX-Bx2REbom.js} +1 -1
  70. package/dist/client/assets/cynefin-OW5HDTMX-Bx2REbom.js.br +0 -0
  71. package/dist/client/assets/cynefin-OW5HDTMX-Bx2REbom.js.gz +0 -0
  72. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-B5c37uFA.js → cynefinDiagram-5FMLGOSQ-DIdCmQka.js} +1 -1
  73. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DIdCmQka.js.br +0 -0
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DIdCmQka.js.gz +0 -0
  75. package/dist/client/assets/{dagre-GXQ25YYZ-DoFMIka4.js → dagre-GXQ25YYZ-6T64o8VI.js} +1 -1
  76. package/dist/client/assets/dagre-GXQ25YYZ-6T64o8VI.js.br +0 -0
  77. package/dist/client/assets/dagre-GXQ25YYZ-6T64o8VI.js.gz +0 -0
  78. package/dist/client/assets/{diagram-S7CK7UJ4-DrAwxGx7.js → diagram-S7CK7UJ4-BenkfLAl.js} +1 -1
  79. package/dist/client/assets/diagram-S7CK7UJ4-BenkfLAl.js.br +0 -0
  80. package/dist/client/assets/diagram-S7CK7UJ4-BenkfLAl.js.gz +0 -0
  81. package/dist/client/assets/{diagram-UQ7AKVKN-DbWfEzKd.js → diagram-UQ7AKVKN-BHobFcQF.js} +1 -1
  82. package/dist/client/assets/diagram-UQ7AKVKN-BHobFcQF.js.br +0 -0
  83. package/dist/client/assets/diagram-UQ7AKVKN-BHobFcQF.js.gz +0 -0
  84. package/dist/client/assets/{diagram-VSXAHHWV-zKcVacRq.js → diagram-VSXAHHWV-dhk5gov4.js} +1 -1
  85. package/dist/client/assets/diagram-VSXAHHWV-dhk5gov4.js.br +0 -0
  86. package/dist/client/assets/diagram-VSXAHHWV-dhk5gov4.js.gz +0 -0
  87. package/dist/client/assets/{diagram-VX7I27RA-BNAI-5sG.js → diagram-VX7I27RA-98uipYMt.js} +1 -1
  88. package/dist/client/assets/diagram-VX7I27RA-98uipYMt.js.br +0 -0
  89. package/dist/client/assets/diagram-VX7I27RA-98uipYMt.js.gz +0 -0
  90. package/dist/client/assets/{diagram-Z3DM3KII-ChB9ZeuK.js → diagram-Z3DM3KII-Bi8PFZ27.js} +1 -1
  91. package/dist/client/assets/diagram-Z3DM3KII-Bi8PFZ27.js.br +0 -0
  92. package/dist/client/assets/diagram-Z3DM3KII-Bi8PFZ27.js.gz +0 -0
  93. package/dist/client/assets/{ebnfDiagram-PWID7BFC-Fe8VFmWJ.js → ebnfDiagram-PWID7BFC-7NoB0jrU.js} +1 -1
  94. package/dist/client/assets/ebnfDiagram-PWID7BFC-7NoB0jrU.js.br +0 -0
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-7NoB0jrU.js.gz +0 -0
  96. package/dist/client/assets/{erDiagram-RLTQ6QDP-CC7DagoX.js → erDiagram-RLTQ6QDP-C7Z9m9Nf.js} +1 -1
  97. package/dist/client/assets/erDiagram-RLTQ6QDP-C7Z9m9Nf.js.br +0 -0
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-C7Z9m9Nf.js.gz +0 -0
  99. package/dist/client/assets/{flowDiagram-HODETNUW-BBIgixMV.js → flowDiagram-HODETNUW-BY1yTBdY.js} +1 -1
  100. package/dist/client/assets/flowDiagram-HODETNUW-BY1yTBdY.js.br +0 -0
  101. package/dist/client/assets/flowDiagram-HODETNUW-BY1yTBdY.js.gz +0 -0
  102. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CHRaIQ9P.js → ganttDiagram-EL5Y4UJY-BExP528m.js} +1 -1
  103. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BExP528m.js.br +0 -0
  104. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CHRaIQ9P.js.gz → ganttDiagram-EL5Y4UJY-BExP528m.js.gz} +0 -0
  105. package/dist/client/assets/{gestureArbiter-5c1oGojb.js → gestureArbiter-BX1L2PZl.js} +1 -1
  106. package/dist/client/assets/{gestureArbiter-5c1oGojb.js.br → gestureArbiter-BX1L2PZl.js.br} +0 -0
  107. package/dist/client/assets/gestureArbiter-BX1L2PZl.js.gz +0 -0
  108. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-iJPivC9C.js → gitGraphDiagram-WWUBYQGX-Dgw8DcAZ.js} +1 -1
  109. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Dgw8DcAZ.js.br +0 -0
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Dgw8DcAZ.js.gz +0 -0
  111. package/dist/client/assets/{infoDiagram-27XIBGKW-CWjxPUb1.js → infoDiagram-27XIBGKW-BtyGlLCl.js} +1 -1
  112. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-2dN_H6WK.js → ishikawaDiagram-5VMMS53U-DHor3FxJ.js} +1 -1
  113. package/dist/client/assets/ishikawaDiagram-5VMMS53U-DHor3FxJ.js.br +0 -0
  114. package/dist/client/assets/ishikawaDiagram-5VMMS53U-DHor3FxJ.js.gz +0 -0
  115. package/dist/client/assets/{journeyDiagram-3NMN7TZE-BqY-OLE2.js → journeyDiagram-3NMN7TZE-zIpSOGqw.js} +1 -1
  116. package/dist/client/assets/journeyDiagram-3NMN7TZE-zIpSOGqw.js.br +0 -0
  117. package/dist/client/assets/journeyDiagram-3NMN7TZE-zIpSOGqw.js.gz +0 -0
  118. package/dist/client/assets/{kanban-definition-UXKFOSKX-CSOXhjNh.js → kanban-definition-UXKFOSKX-Db-J_3Ke.js} +1 -1
  119. package/dist/client/assets/kanban-definition-UXKFOSKX-Db-J_3Ke.js.br +0 -0
  120. package/dist/client/assets/kanban-definition-UXKFOSKX-Db-J_3Ke.js.gz +0 -0
  121. package/dist/client/assets/{layout-BxIcRRf7.js → layout-fhC50xhx.js} +1 -1
  122. package/dist/client/assets/layout-fhC50xhx.js.br +0 -0
  123. package/dist/client/assets/layout-fhC50xhx.js.gz +0 -0
  124. package/dist/client/assets/{linear-BYWiw-Yd.js → linear-cRh-kitO.js} +1 -1
  125. package/dist/client/assets/linear-cRh-kitO.js.br +0 -0
  126. package/dist/client/assets/linear-cRh-kitO.js.gz +0 -0
  127. package/dist/client/assets/{mermaid.core-DhRpdkKn.js → mermaid.core-BJ-BnV2O.js} +5 -5
  128. package/dist/client/assets/mermaid.core-BJ-BnV2O.js.br +0 -0
  129. package/dist/client/assets/mermaid.core-BJ-BnV2O.js.gz +0 -0
  130. package/dist/client/assets/{mindmap-definition-YA3MSWOX-DwBcGwTF.js → mindmap-definition-YA3MSWOX-CASwnewq.js} +1 -1
  131. package/dist/client/assets/mindmap-definition-YA3MSWOX-CASwnewq.js.br +0 -0
  132. package/dist/client/assets/mindmap-definition-YA3MSWOX-CASwnewq.js.gz +0 -0
  133. package/dist/client/assets/{passwordBootstrap-BkAa-DO2.js → passwordBootstrap-BUCqUzVV.js} +1 -1
  134. package/dist/client/assets/passwordBootstrap-BUCqUzVV.js.br +0 -0
  135. package/dist/client/assets/passwordBootstrap-BUCqUzVV.js.gz +0 -0
  136. package/dist/client/assets/{pegDiagram-XKGWAZYB-D_04AQMs.js → pegDiagram-XKGWAZYB-DvxIsNki.js} +1 -1
  137. package/dist/client/assets/pegDiagram-XKGWAZYB-DvxIsNki.js.br +0 -0
  138. package/dist/client/assets/pegDiagram-XKGWAZYB-DvxIsNki.js.gz +0 -0
  139. package/dist/client/assets/{pieDiagram-E7YTZNPT-DgHIGdDc.js → pieDiagram-E7YTZNPT-Cd5KTCKr.js} +1 -1
  140. package/dist/client/assets/pieDiagram-E7YTZNPT-Cd5KTCKr.js.br +0 -0
  141. package/dist/client/assets/pieDiagram-E7YTZNPT-Cd5KTCKr.js.gz +0 -0
  142. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BrxP_uCp.js → quadrantDiagram-AXDQQJYC-B2Dzl9r4.js} +1 -1
  143. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B2Dzl9r4.js.br +0 -0
  144. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B2Dzl9r4.js.gz +0 -0
  145. package/dist/client/assets/{railroadDiagram-O6MQD6OU-Bj8aV8et.js → railroadDiagram-O6MQD6OU-0k2P5vxC.js} +1 -1
  146. package/dist/client/assets/railroadDiagram-O6MQD6OU-0k2P5vxC.js.br +0 -0
  147. package/dist/client/assets/railroadDiagram-O6MQD6OU-0k2P5vxC.js.gz +0 -0
  148. package/dist/client/assets/{referenceLongPress-CG4k8F2W.js → referenceLongPress-DiAiVNRD.js} +1 -1
  149. package/dist/client/assets/referenceLongPress-DiAiVNRD.js.br +0 -0
  150. package/dist/client/assets/referenceLongPress-DiAiVNRD.js.gz +0 -0
  151. package/dist/client/assets/{requirementDiagram-BXWQKSXE-DHOBnmaH.js → requirementDiagram-BXWQKSXE-q7DHcUvb.js} +1 -1
  152. package/dist/client/assets/requirementDiagram-BXWQKSXE-q7DHcUvb.js.br +0 -0
  153. package/dist/client/assets/requirementDiagram-BXWQKSXE-q7DHcUvb.js.gz +0 -0
  154. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-B46rNwzQ.js → sankeyDiagram-P5KCCOFB-Bl1JWXJq.js} +1 -1
  155. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bl1JWXJq.js.br +0 -0
  156. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bl1JWXJq.js.gz +0 -0
  157. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-D8PnDgC6.js → sequenceDiagram-WJ2MYXX4-BrEcjCaW.js} +1 -1
  158. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BrEcjCaW.js.br +0 -0
  159. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BrEcjCaW.js.gz +0 -0
  160. package/dist/client/assets/{sizeCapture-INFHLROL-CjofpSnZ.js → sizeCapture-INFHLROL-JIXpO6YT.js} +1 -1
  161. package/dist/client/assets/sizeCapture-INFHLROL-JIXpO6YT.js.br +0 -0
  162. package/dist/client/assets/sizeCapture-INFHLROL-JIXpO6YT.js.gz +0 -0
  163. package/dist/client/assets/{sparkles-Zi8g0TMb.js → sparkles-Cr5Ep9Lu.js} +1 -1
  164. package/dist/client/assets/{stateDiagram-D77RDMKH-BX_StFJV.js → stateDiagram-D77RDMKH-uz_XpHV7.js} +1 -1
  165. package/dist/client/assets/stateDiagram-D77RDMKH-uz_XpHV7.js.br +0 -0
  166. package/dist/client/assets/stateDiagram-D77RDMKH-uz_XpHV7.js.gz +0 -0
  167. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-DLePl8jh.js +1 -0
  168. package/dist/client/assets/{swimlanes-42K2YHIH-bxGulCYh.js → swimlanes-42K2YHIH-BYE9uO28.js} +1 -1
  169. package/dist/client/assets/swimlanes-42K2YHIH-BYE9uO28.js.br +0 -0
  170. package/dist/client/assets/swimlanes-42K2YHIH-BYE9uO28.js.gz +0 -0
  171. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-4NCunUpJ.js +8 -0
  172. package/dist/client/assets/{timeline-definition-24CTP7MA-DcHYPI0d.js → timeline-definition-24CTP7MA-46Pz3ht4.js} +1 -1
  173. package/dist/client/assets/timeline-definition-24CTP7MA-46Pz3ht4.js.br +0 -0
  174. package/dist/client/assets/{timeline-definition-24CTP7MA-DcHYPI0d.js.gz → timeline-definition-24CTP7MA-46Pz3ht4.js.gz} +0 -0
  175. package/dist/client/assets/{triangle-alert-BXXcxTk8.js → triangle-alert-CD2hoUd0.js} +1 -1
  176. package/dist/client/assets/{vennDiagram-4TSXK5OY-CjIGwkZf.js → vennDiagram-4TSXK5OY-K0fm406P.js} +1 -1
  177. package/dist/client/assets/{vennDiagram-4TSXK5OY-CjIGwkZf.js.br → vennDiagram-4TSXK5OY-K0fm406P.js.br} +0 -0
  178. package/dist/client/assets/vennDiagram-4TSXK5OY-K0fm406P.js.gz +0 -0
  179. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-B-WK3Fil.js → wardleyDiagram-VM6X3IG4-Defom9Go.js} +1 -1
  180. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Defom9Go.js.br +0 -0
  181. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Defom9Go.js.gz +0 -0
  182. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-4RzKQfNK.js → xychartDiagram-S5SC5T6Z-DjylOW-i.js} +1 -1
  183. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DjylOW-i.js.br +0 -0
  184. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DjylOW-i.js.gz +0 -0
  185. package/dist/client/index.html +1 -1
  186. package/dist/client/index.html.br +0 -0
  187. package/dist/client/index.html.gz +0 -0
  188. package/dist/client/sw.js +1 -1
  189. package/dist/client/sw.js.br +0 -0
  190. package/dist/client/sw.js.gz +0 -0
  191. package/dist/server/certificateWatcher.js +7 -29
  192. package/dist/server/cli.js +58 -83
  193. package/dist/server/entry.js +24 -2
  194. package/dist/server/federation/relay.js +3 -0
  195. package/dist/server/federation/routeAccess.js +39 -2
  196. package/dist/server/federation/runtime.js +5 -0
  197. package/dist/server/federation/selfRelay.js +182 -0
  198. package/dist/server/utils/certificateNames.js +12 -0
  199. package/package.json +1 -1
  200. package/runtime-manifest.json +3 -3
  201. package/dist/client/assets/App-CmlGDKDj.js.br +0 -0
  202. package/dist/client/assets/App-CmlGDKDj.js.gz +0 -0
  203. package/dist/client/assets/ChangeWalkthroughPanel-9whVVXTr.js.br +0 -0
  204. package/dist/client/assets/ChangeWalkthroughPanel-9whVVXTr.js.gz +0 -0
  205. package/dist/client/assets/DagPlayground-y80OTTQj.js.br +0 -0
  206. package/dist/client/assets/DagPlayground-y80OTTQj.js.gz +0 -0
  207. package/dist/client/assets/DiffLab-BY2wbRtg.js.br +0 -0
  208. package/dist/client/assets/DiffLab-BY2wbRtg.js.gz +0 -0
  209. package/dist/client/assets/DiffReview-DLlDJbK1.js.br +0 -0
  210. package/dist/client/assets/DiffReview-DLlDJbK1.js.gz +0 -0
  211. package/dist/client/assets/DiffReviewLab-CEpa_B8I.js.br +0 -0
  212. package/dist/client/assets/DiffReviewLab-CEpa_B8I.js.gz +0 -0
  213. package/dist/client/assets/DiffViewer-GLkd1zMQ.js.br +0 -0
  214. package/dist/client/assets/DiffViewer-GLkd1zMQ.js.gz +0 -0
  215. package/dist/client/assets/ModelPreview-DB8tw45f.js.br +0 -0
  216. package/dist/client/assets/ModelPreview-DB8tw45f.js.gz +0 -0
  217. package/dist/client/assets/RightSidebar-aoJjOQGX.js.br +0 -0
  218. package/dist/client/assets/RightSidebar-aoJjOQGX.js.gz +0 -0
  219. package/dist/client/assets/abnfDiagram-VCTEODGH-RBGYciHa.js.br +0 -0
  220. package/dist/client/assets/abnfDiagram-VCTEODGH-RBGYciHa.js.gz +0 -0
  221. package/dist/client/assets/app-Ctrh4Bku.js.br +0 -0
  222. package/dist/client/assets/app-Ctrh4Bku.js.gz +0 -0
  223. package/dist/client/assets/arc-CzQsNCup.js.br +0 -0
  224. package/dist/client/assets/arc-CzQsNCup.js.gz +0 -0
  225. package/dist/client/assets/architectureDiagram-5GKGNRK7-sDp6hSiy.js.gz +0 -0
  226. package/dist/client/assets/blockDiagram-I7D4REHJ-C-AHqZPo.js.br +0 -0
  227. package/dist/client/assets/blockDiagram-I7D4REHJ-C-AHqZPo.js.gz +0 -0
  228. package/dist/client/assets/c4Diagram-7LVT6UL2-B8VGhLwN.js.br +0 -0
  229. package/dist/client/assets/c4Diagram-7LVT6UL2-B8VGhLwN.js.gz +0 -0
  230. package/dist/client/assets/channel-zK78er-R.js +0 -1
  231. package/dist/client/assets/chunk-F27PBJKO-BNOR_7WA.js.br +0 -0
  232. package/dist/client/assets/chunk-F27PBJKO-BNOR_7WA.js.gz +0 -0
  233. package/dist/client/assets/chunk-IMKFNOWR-Dk12DKtC.js.br +0 -0
  234. package/dist/client/assets/chunk-SVP7TREG-BIJtdFBL.js.br +0 -0
  235. package/dist/client/assets/chunk-SVP7TREG-BIJtdFBL.js.gz +0 -0
  236. package/dist/client/assets/chunk-TICWLB2K-CS3jsxv7.js.br +0 -0
  237. package/dist/client/assets/chunk-TICWLB2K-CS3jsxv7.js.gz +0 -0
  238. package/dist/client/assets/classDiagram-ZZMXUADV-BMh5Sak3.js +0 -1
  239. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BMh5Sak3.js +0 -1
  240. package/dist/client/assets/cose-bilkent-JH36ORCC-9udJiW7h.js.br +0 -0
  241. package/dist/client/assets/cose-bilkent-JH36ORCC-9udJiW7h.js.gz +0 -0
  242. package/dist/client/assets/cynefin-OW5HDTMX-D-6q-6N2.js.br +0 -0
  243. package/dist/client/assets/cynefin-OW5HDTMX-D-6q-6N2.js.gz +0 -0
  244. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B5c37uFA.js.br +0 -0
  245. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B5c37uFA.js.gz +0 -0
  246. package/dist/client/assets/dagre-GXQ25YYZ-DoFMIka4.js.br +0 -0
  247. package/dist/client/assets/dagre-GXQ25YYZ-DoFMIka4.js.gz +0 -0
  248. package/dist/client/assets/diagram-S7CK7UJ4-DrAwxGx7.js.br +0 -0
  249. package/dist/client/assets/diagram-S7CK7UJ4-DrAwxGx7.js.gz +0 -0
  250. package/dist/client/assets/diagram-UQ7AKVKN-DbWfEzKd.js.br +0 -0
  251. package/dist/client/assets/diagram-UQ7AKVKN-DbWfEzKd.js.gz +0 -0
  252. package/dist/client/assets/diagram-VSXAHHWV-zKcVacRq.js.br +0 -0
  253. package/dist/client/assets/diagram-VSXAHHWV-zKcVacRq.js.gz +0 -0
  254. package/dist/client/assets/diagram-VX7I27RA-BNAI-5sG.js.br +0 -0
  255. package/dist/client/assets/diagram-VX7I27RA-BNAI-5sG.js.gz +0 -0
  256. package/dist/client/assets/diagram-Z3DM3KII-ChB9ZeuK.js.br +0 -0
  257. package/dist/client/assets/diagram-Z3DM3KII-ChB9ZeuK.js.gz +0 -0
  258. package/dist/client/assets/ebnfDiagram-PWID7BFC-Fe8VFmWJ.js.br +0 -0
  259. package/dist/client/assets/ebnfDiagram-PWID7BFC-Fe8VFmWJ.js.gz +0 -0
  260. package/dist/client/assets/erDiagram-RLTQ6QDP-CC7DagoX.js.br +0 -0
  261. package/dist/client/assets/erDiagram-RLTQ6QDP-CC7DagoX.js.gz +0 -0
  262. package/dist/client/assets/flowDiagram-HODETNUW-BBIgixMV.js.br +0 -0
  263. package/dist/client/assets/flowDiagram-HODETNUW-BBIgixMV.js.gz +0 -0
  264. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CHRaIQ9P.js.br +0 -0
  265. package/dist/client/assets/gestureArbiter-5c1oGojb.js.gz +0 -0
  266. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-iJPivC9C.js.br +0 -0
  267. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-iJPivC9C.js.gz +0 -0
  268. package/dist/client/assets/ishikawaDiagram-5VMMS53U-2dN_H6WK.js.br +0 -0
  269. package/dist/client/assets/ishikawaDiagram-5VMMS53U-2dN_H6WK.js.gz +0 -0
  270. package/dist/client/assets/journeyDiagram-3NMN7TZE-BqY-OLE2.js.br +0 -0
  271. package/dist/client/assets/journeyDiagram-3NMN7TZE-BqY-OLE2.js.gz +0 -0
  272. package/dist/client/assets/kanban-definition-UXKFOSKX-CSOXhjNh.js.br +0 -0
  273. package/dist/client/assets/kanban-definition-UXKFOSKX-CSOXhjNh.js.gz +0 -0
  274. package/dist/client/assets/layout-BxIcRRf7.js.br +0 -0
  275. package/dist/client/assets/layout-BxIcRRf7.js.gz +0 -0
  276. package/dist/client/assets/linear-BYWiw-Yd.js.br +0 -0
  277. package/dist/client/assets/linear-BYWiw-Yd.js.gz +0 -0
  278. package/dist/client/assets/mermaid.core-DhRpdkKn.js.br +0 -0
  279. package/dist/client/assets/mermaid.core-DhRpdkKn.js.gz +0 -0
  280. package/dist/client/assets/mindmap-definition-YA3MSWOX-DwBcGwTF.js.br +0 -0
  281. package/dist/client/assets/mindmap-definition-YA3MSWOX-DwBcGwTF.js.gz +0 -0
  282. package/dist/client/assets/passwordBootstrap-BkAa-DO2.js.br +0 -0
  283. package/dist/client/assets/passwordBootstrap-BkAa-DO2.js.gz +0 -0
  284. package/dist/client/assets/pegDiagram-XKGWAZYB-D_04AQMs.js.br +0 -0
  285. package/dist/client/assets/pegDiagram-XKGWAZYB-D_04AQMs.js.gz +0 -0
  286. package/dist/client/assets/pieDiagram-E7YTZNPT-DgHIGdDc.js.br +0 -0
  287. package/dist/client/assets/pieDiagram-E7YTZNPT-DgHIGdDc.js.gz +0 -0
  288. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BrxP_uCp.js.br +0 -0
  289. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BrxP_uCp.js.gz +0 -0
  290. package/dist/client/assets/railroadDiagram-O6MQD6OU-Bj8aV8et.js.br +0 -0
  291. package/dist/client/assets/railroadDiagram-O6MQD6OU-Bj8aV8et.js.gz +0 -0
  292. package/dist/client/assets/referenceLongPress-CG4k8F2W.js.br +0 -0
  293. package/dist/client/assets/referenceLongPress-CG4k8F2W.js.gz +0 -0
  294. package/dist/client/assets/requirementDiagram-BXWQKSXE-DHOBnmaH.js.br +0 -0
  295. package/dist/client/assets/requirementDiagram-BXWQKSXE-DHOBnmaH.js.gz +0 -0
  296. package/dist/client/assets/sankeyDiagram-P5KCCOFB-B46rNwzQ.js.br +0 -0
  297. package/dist/client/assets/sankeyDiagram-P5KCCOFB-B46rNwzQ.js.gz +0 -0
  298. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D8PnDgC6.js.br +0 -0
  299. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D8PnDgC6.js.gz +0 -0
  300. package/dist/client/assets/sizeCapture-INFHLROL-CjofpSnZ.js.br +0 -0
  301. package/dist/client/assets/sizeCapture-INFHLROL-CjofpSnZ.js.gz +0 -0
  302. package/dist/client/assets/stateDiagram-D77RDMKH-BX_StFJV.js.br +0 -0
  303. package/dist/client/assets/stateDiagram-D77RDMKH-BX_StFJV.js.gz +0 -0
  304. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CMG96YGj.js +0 -1
  305. package/dist/client/assets/swimlanes-42K2YHIH-bxGulCYh.js.br +0 -0
  306. package/dist/client/assets/swimlanes-42K2YHIH-bxGulCYh.js.gz +0 -0
  307. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-sPKOywKp.js +0 -8
  308. package/dist/client/assets/timeline-definition-24CTP7MA-DcHYPI0d.js.br +0 -0
  309. package/dist/client/assets/vennDiagram-4TSXK5OY-CjIGwkZf.js.gz +0 -0
  310. package/dist/client/assets/wardleyDiagram-VM6X3IG4-B-WK3Fil.js.br +0 -0
  311. package/dist/client/assets/wardleyDiagram-VM6X3IG4-B-WK3Fil.js.gz +0 -0
  312. package/dist/client/assets/xychartDiagram-S5SC5T6Z-4RzKQfNK.js.br +0 -0
  313. package/dist/client/assets/xychartDiagram-S5SC5T6Z-4RzKQfNK.js.gz +0 -0
@@ -20,6 +20,7 @@ import { detectLocalSessionContext, resolveLocalCollaborationContext } from './a
20
20
  import { parseAutomationCommand, executeAutomationCommand, AUTOMATION_HELP } from './agent/automationCli.js';
21
21
  import { parseCollaborationCommand, executeCollaborationCommand, COLLAB_HELP } from './agent/collaborationCli.js';
22
22
  import fs from 'fs';
23
+ import { getMissingCertificateNames } from './utils/certificateNames.js';
23
24
  import http from 'http';
24
25
  import https from 'https';
25
26
  import path from 'path';
@@ -323,19 +324,6 @@ function getOrCreateLocalApiToken() {
323
324
  fs.writeFileSync(localApiTokenPath, `${token}\n`, { mode: 0o600 });
324
325
  return token;
325
326
  }
326
- function buildServerState(params) {
327
- return {
328
- pid: params.pid,
329
- host: params.host,
330
- port: params.port,
331
- scheme: params.scheme,
332
- localUrl: `${params.scheme}://${params.host === '0.0.0.0' ? 'localhost' : params.host}:${params.port}`,
333
- localAccessReason: params.localAccessReason,
334
- logFile: logFilePath,
335
- startedAt: new Date().toISOString(),
336
- localApiToken: params.localApiToken,
337
- };
338
- }
339
327
  function fileExists(filePath) {
340
328
  return typeof filePath === 'string' && filePath.length > 0 && fs.existsSync(filePath);
341
329
  }
@@ -417,50 +405,10 @@ function getRequiredLocalHttpsNames() {
417
405
  '::1',
418
406
  ];
419
407
  }
420
- async function readCertificateSans(certPath) {
421
- try {
422
- const { stdout } = await execFileAsync('openssl', ['x509', '-in', certPath, '-noout', '-ext', 'subjectAltName'], {
423
- timeout: 5000,
424
- maxBuffer: 256 * 1024,
425
- });
426
- return stdout
427
- .split(/[\n,]/)
428
- .map((part) => part.trim())
429
- .filter(Boolean);
430
- }
431
- catch {
432
- return [];
433
- }
434
- }
435
- function sanMatches(required, sans) {
436
- if (/^\d+\.\d+\.\d+\.\d+$/.test(required)) {
437
- return sans.some((san) => san === `IP Address:${required}` || san === `IP:${required}`);
438
- }
439
- if (required.includes(':')) {
440
- const normalizeIpv6 = (value) => {
441
- const [left = '', right = ''] = value.toLowerCase().split('::');
442
- const leftGroups = left ? left.split(':') : [];
443
- const rightGroups = right ? right.split(':') : [];
444
- const missing = Math.max(0, 8 - leftGroups.length - rightGroups.length);
445
- return [...leftGroups, ...Array(missing).fill('0'), ...rightGroups]
446
- .map((group) => Number.parseInt(group || '0', 16).toString(16))
447
- .join(':');
448
- };
449
- const normalizedRequired = normalizeIpv6(required);
450
- return sans.some((san) => {
451
- const match = /^(?:IP Address|IP):(.+)$/.exec(san);
452
- return match ? normalizeIpv6(match[1]) === normalizedRequired : false;
453
- });
454
- }
455
- return sans.some((san) => san === `DNS:${required}`);
456
- }
457
408
  async function defaultCertificateNeedsRefresh() {
458
409
  if (!fileExists(defaultHttpsCertPath) || !fileExists(defaultHttpsKeyPath))
459
410
  return true;
460
- const sans = await readCertificateSans(defaultHttpsCertPath);
461
- if (sans.length === 0)
462
- return true;
463
- return getRequiredLocalHttpsNames().some((name) => !sanMatches(name, sans));
411
+ return (await getMissingCertificateNames(defaultHttpsCertPath, getRequiredLocalHttpsNames())).length > 0;
464
412
  }
465
413
  async function ensureDefaultHttpsCertificateFresh() {
466
414
  if (!(await defaultCertificateNeedsRefresh()))
@@ -1443,12 +1391,19 @@ const STOP_GRACE_MS = 5000;
1443
1391
  /** Max time to wait for the daemon child to respond to /health after spawn. */
1444
1392
  const DAEMON_START_TIMEOUT_MS = 10000;
1445
1393
  /** Poll the health endpoint until it responds or timeout expires. */
1446
- async function waitForHealth(healthUrl, caPath, timeoutMs) {
1394
+ async function waitForHealth(healthUrl, caPath, timeoutMs, isReady, isAlive) {
1447
1395
  const deadline = Date.now() + timeoutMs;
1448
1396
  while (Date.now() < deadline) {
1397
+ if (!isAlive())
1398
+ return false;
1399
+ if (!isReady()) {
1400
+ await new Promise((resolve) => setTimeout(resolve, 100));
1401
+ continue;
1402
+ }
1449
1403
  try {
1450
1404
  const ok = await new Promise((resolve) => {
1451
- const req = https.get(healthUrl, {
1405
+ const transport = healthUrl.startsWith('https:') ? https : http;
1406
+ const req = transport.get(healthUrl, {
1452
1407
  ca: caPath ? fs.readFileSync(caPath) : undefined,
1453
1408
  rejectUnauthorized: Boolean(caPath),
1454
1409
  timeout: 2000,
@@ -1460,7 +1415,7 @@ async function waitForHealth(healthUrl, caPath, timeoutMs) {
1460
1415
  req.on('error', () => resolve(false));
1461
1416
  req.on('timeout', () => { req.destroy(); resolve(false); });
1462
1417
  });
1463
- if (ok)
1418
+ if (ok && isAlive())
1464
1419
  return true;
1465
1420
  }
1466
1421
  catch {
@@ -3473,10 +3428,6 @@ async function main() {
3473
3428
  await runPluginRemove(options.pluginRemove);
3474
3429
  process.exit(0);
3475
3430
  }
3476
- const localApiToken = getOrCreateLocalApiToken();
3477
- await refreshDefaultHttpsCertificateSafely();
3478
- const https = resolveHttpsOptions(options);
3479
- const isManagedDefaultHttps = Boolean(https.cert === defaultHttpsCertPath && https.key === defaultHttpsKeyPath);
3480
3431
  if (options.status) {
3481
3432
  const runningState = getRunningState();
3482
3433
  if (!runningState) {
@@ -3513,6 +3464,10 @@ async function main() {
3513
3464
  }
3514
3465
  process.exit(0);
3515
3466
  }
3467
+ const localApiToken = getOrCreateLocalApiToken();
3468
+ await refreshDefaultHttpsCertificateSafely();
3469
+ const https = resolveHttpsOptions(options);
3470
+ const isManagedDefaultHttps = Boolean(https.cert === defaultHttpsCertPath && https.key === defaultHttpsKeyPath);
3516
3471
  if (options.foreground) {
3517
3472
  // Boot check (marker prevents re-run if parent already completed)
3518
3473
  runBootChecks();
@@ -3582,6 +3537,20 @@ async function main() {
3582
3537
  startedAt: new Date().toISOString(),
3583
3538
  localApiToken,
3584
3539
  });
3540
+ // Only this child can acknowledge startup; an existing listener on the
3541
+ // same port must not make the parent report a successful launch.
3542
+ const acknowledgeStartup = () => {
3543
+ if (process.connected) {
3544
+ process.send?.({ type: 'termdock-ready' }, () => {
3545
+ if (process.connected)
3546
+ process.disconnect();
3547
+ });
3548
+ }
3549
+ };
3550
+ if (result.server.listening)
3551
+ acknowledgeStartup();
3552
+ else
3553
+ result.server.once('listening', acknowledgeStartup);
3585
3554
  return;
3586
3555
  }
3587
3556
  const runningState = getRunningState();
@@ -3622,24 +3591,40 @@ async function main() {
3622
3591
  childArgs.push('--https-ca', activeHttps.ca);
3623
3592
  }
3624
3593
  const scheme = activeHttps.cert && activeHttps.key ? 'https' : 'http';
3625
- const localAccessReason = activeHttps.source === 'default'
3626
- ? 'Using local HTTPS certificates from ~/.termdock/certs.'
3627
- : null;
3628
3594
  const logFileFd = fs.openSync(logFilePath, 'a');
3629
3595
  const child = spawn(process.execPath, childArgs, {
3630
3596
  detached: true,
3631
- stdio: ['ignore', logFileFd, logFileFd],
3597
+ stdio: ['ignore', logFileFd, logFileFd, 'ipc'],
3632
3598
  });
3633
3599
  fs.closeSync(logFileFd);
3600
+ let ready = false;
3601
+ let startupError;
3602
+ child.on('error', (error) => { startupError = error; });
3603
+ child.on('message', (message) => {
3604
+ if (message && typeof message === 'object' && 'type' in message
3605
+ && message.type === 'termdock-ready')
3606
+ ready = true;
3607
+ });
3608
+ const isAlive = () => !startupError && child.pid !== undefined
3609
+ && child.exitCode === null && child.signalCode === null && isProcessRunning(child.pid);
3610
+ const displayHost = childHost === '0.0.0.0' ? 'localhost' : childHost;
3611
+ const healthOk = await waitForHealth(`${scheme}://${displayHost}:${childPort}/health`, activeHttps.ca, DAEMON_START_TIMEOUT_MS, () => ready, isAlive);
3612
+ if (child.connected)
3613
+ child.disconnect();
3634
3614
  child.unref();
3635
- writeState(buildServerState({
3636
- pid: child.pid,
3637
- host: childHost,
3638
- port: childPort,
3639
- scheme,
3640
- localAccessReason,
3641
- localApiToken,
3642
- }));
3615
+ if (!healthOk) {
3616
+ const alive = isAlive();
3617
+ if (!alive && child.pid !== undefined && readState()?.pid === child.pid)
3618
+ removeStateFile();
3619
+ const detail = startupError?.message ?? (alive
3620
+ ? 'Startup timed out; the process is still running but readiness could not be confirmed.'
3621
+ : `Background process exited (code: ${child.exitCode ?? 'unknown'}, signal: ${child.signalCode ?? 'none'}).`);
3622
+ console.error(`${ICON.err} ${c.red('Termdock startup could not be confirmed.')} ${detail}`);
3623
+ console.error(` ${c.dim('Log:')} ${logFilePath}`);
3624
+ console.error(` Read the startup error with: tail -n 80 ${shellQuote(logFilePath)}`);
3625
+ process.exitCode = 1;
3626
+ return;
3627
+ }
3643
3628
  console.log(`${ICON.ok} ${c.green('Termdock started in background.')}`);
3644
3629
  console.log(` ${c.dim('URL:')} ${c.cyan(`${scheme}://${childHost === '0.0.0.0' ? 'localhost' : childHost}:${childPort}`)}`);
3645
3630
  if (scheme === 'https') {
@@ -3651,16 +3636,6 @@ async function main() {
3651
3636
  console.log(` ${c.dim('PID:')} ${child.pid}`);
3652
3637
  console.log(` ${c.dim('Log:')} ${logFilePath}`);
3653
3638
  warnIfAuthDisabled(childHost);
3654
- // Wait for the daemon child to become healthy before returning — a
3655
- // subsequent deploy step that curls the server won't race startup.
3656
- {
3657
- const displayHost = childHost === '0.0.0.0' ? 'localhost' : childHost;
3658
- const healthUrl = `${scheme}://${displayHost}:${childPort}/health`;
3659
- const healthOk = await waitForHealth(healthUrl, activeHttps.ca, DAEMON_START_TIMEOUT_MS);
3660
- if (!healthOk) {
3661
- console.log(`${ICON.warn} ${c.yellow('Server started but health check did not respond in time — it may still be initializing.')}`);
3662
- }
3663
- }
3664
3639
  process.exit(0);
3665
3640
  }
3666
3641
  main().catch((err) => {
@@ -14,6 +14,7 @@ import { RouteInvitationStore } from './federation/routeInvitations.js';
14
14
  import { attachRegisteredDirectTargets, verifyDirectTarget } from './federation/directRoutes.js';
15
15
  import { secureChannelOriginAllowed } from './federation/originPolicy.js';
16
16
  import { RelayRouter } from './federation/relay.js';
17
+ import { DesktopSelfRelay } from './federation/selfRelay.js';
17
18
  import { assertPublicSecurity, securityHeaders } from './utils/publicSecurity.js';
18
19
  import { apiCachePolicy } from './utils/apiCachePolicy.js';
19
20
  import 'dotenv/config';
@@ -415,13 +416,15 @@ export function startServer(options = {}) {
415
416
  let entrySubjectAllowed = (_subjectId) => false;
416
417
  let routeInvitations;
417
418
  let federationServiceId = '';
419
+ let activeRouteIds = () => [];
418
420
  const desktopTargets = () => desktopDirectTargets(path.join(homedir(), '.termdock', 'desktop.json'), federationServiceId);
419
- const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true || collaborationTransport?.canRoute(subjectId, targetServiceId) === true, desktopTargets);
421
+ const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true || collaborationTransport?.canRoute(subjectId, targetServiceId) === true, desktopTargets, () => activeRouteIds());
420
422
  const relayRouter = new RelayRouter({
421
423
  authenticate: context => context,
422
424
  allowRegister: (principal, serviceId) => routeAccess.allowRegister(principal, serviceId),
423
425
  allowRoute: (principal, serviceId) => routeAccess.allowRoute(principal, serviceId),
424
426
  });
427
+ activeRouteIds = () => relayRouter.activeRouteIds();
425
428
  const dynamicDirectTargets = new Map();
426
429
  const refreshDirectTargets = () => {
427
430
  const desired = new Map(routeAccess.configuredDirectTargets().filter(target => target.serviceId !== federationServiceId).map(target => [target.serviceId, target]));
@@ -493,6 +496,7 @@ export function startServer(options = {}) {
493
496
  },
494
497
  revokeRouteAccess: id => routeInvitations?.revoke(id) || false,
495
498
  issueRouteTicket: (subjectId, serviceId) => routeAccess.issueRouteTicket(subjectId, serviceId),
499
+ issueSelfRelayTicket: (serviceId) => routeAccess.issueSelfRelayTicket(serviceId),
496
500
  hasRouteGrant: (subjectId, serviceId) => routeInvitations?.allows(subjectId, serviceId) === true || collaborationTransport?.canRoute(subjectId, serviceId) === true,
497
501
  createRouteInvitation: (issuerId, serviceId) => {
498
502
  if (!routeInvitations)
@@ -567,6 +571,20 @@ export function startServer(options = {}) {
567
571
  issuerAllowed: subjectId => entrySubjectAllowed(subjectId),
568
572
  targetAvailable: serviceId => routeAccess.hasConfiguredTarget(serviceId) && relayRouter.hasRoute(serviceId),
569
573
  });
574
+ let localCa;
575
+ if (scheme === 'https' && options.httpsCaPath)
576
+ try {
577
+ localCa = fs.readFileSync(options.httpsCaPath);
578
+ }
579
+ catch { /* System trust may still validate the local certificate. */ }
580
+ const localHostname = ['0.0.0.0', '::'].includes(host) ? 'localhost' : host;
581
+ const localAuthority = localHostname.includes(':') && !localHostname.startsWith('[') ? `[${localHostname}]` : localHostname;
582
+ const selfRelay = new DesktopSelfRelay({
583
+ desktopFile: path.join(homedir(), '.termdock', 'desktop.json'), identity: runtime.identity, serviceId: runtime.serviceId,
584
+ localOrigin: `${scheme}://${localAuthority}:${port}`, ...(localCa ? { localCa } : {}),
585
+ });
586
+ selfRelay.start();
587
+ server.once('close', () => selfRelay.close());
570
588
  }).catch(() => console.error('Route invitations unavailable; uninitialized route grants remain denied.'));
571
589
  void federation.catch(() => console.error('Encrypted access initialization failed; business access remains closed.'));
572
590
  server.once('close', () => { void federation.then(runtime => runtime.close()).catch(() => { }); });
@@ -661,7 +679,11 @@ export function startServer(options = {}) {
661
679
  socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
662
680
  return;
663
681
  }
664
- const principal = routeAccess.authenticate(request.headers.authorization, url.searchParams.get('routeToken'));
682
+ const routeToken = url.searchParams.get('routeToken');
683
+ const selfRelayToken = url.searchParams.get('selfRelayToken');
684
+ const principal = selfRelayToken && !request.headers.authorization && !routeToken
685
+ ? routeAccess.authenticateSelfRelay(selfRelayToken)
686
+ : !selfRelayToken ? routeAccess.authenticate(request.headers.authorization, routeToken) : null;
665
687
  if (!principal) {
666
688
  socket.end('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
667
689
  return;
@@ -111,6 +111,9 @@ export class RelayRouter {
111
111
  const route = this.routes.get(serviceId);
112
112
  return !this.stopped && !!route && route.expires > Date.now() && route.peer.ws.readyState === 1 && this.canRegister(route.peer, serviceId);
113
113
  }
114
+ activeRouteIds() {
115
+ return [...this.routes.keys()].filter(serviceId => this.hasRoute(serviceId));
116
+ }
114
117
  /** Trusted local configuration only. Clients never supply a target URL or factory. */
115
118
  registerDirect(serviceId, connect) {
116
119
  if (this.stopped || !identifier(serviceId) || typeof connect !== 'function' || this.hasRoute(serviceId))
@@ -9,12 +9,15 @@ export class RouteAccess {
9
9
  now;
10
10
  canRouteSubject;
11
11
  additionalTargets;
12
+ additionalTargetIds;
12
13
  tickets = new Map();
13
- constructor(filePath, now = Date.now, canRouteSubject = () => false, additionalTargets = () => []) {
14
+ selfRelayTickets = new Map();
15
+ constructor(filePath, now = Date.now, canRouteSubject = () => false, additionalTargets = () => [], additionalTargetIds = () => []) {
14
16
  this.filePath = filePath;
15
17
  this.now = now;
16
18
  this.canRouteSubject = canRouteSubject;
17
19
  this.additionalTargets = additionalTargets;
20
+ this.additionalTargetIds = additionalTargetIds;
18
21
  }
19
22
  registry() {
20
23
  const empty = { relays: [], directTargets: [] };
@@ -62,6 +65,34 @@ export class RouteAccess {
62
65
  this.tickets.set(digest(routeToken).toString('hex'), { subjectId, serviceId, expiresAt });
63
66
  return { routeToken, expiresAt };
64
67
  }
68
+ /** A Noise-authenticated service can advertise only its own identity. The
69
+ * short-lived token merely transfers that identity onto the relay socket; it
70
+ * grants neither route use nor target-service access. */
71
+ issueSelfRelayTicket(serviceId) {
72
+ if (!validId(serviceId))
73
+ throw new Error('INVALID_SELF_RELAY_IDENTITY');
74
+ for (const [key, value] of this.selfRelayTickets)
75
+ if (value.expiresAt <= this.now())
76
+ this.selfRelayTickets.delete(key);
77
+ if (this.selfRelayTickets.size >= 1024)
78
+ throw new Error('SELF_RELAY_TICKET_LIMIT');
79
+ const selfRelayToken = randomBytes(32).toString('base64url');
80
+ const expiresAt = this.now() + 30_000;
81
+ this.selfRelayTickets.set(digest(selfRelayToken).toString('hex'), { serviceId, expiresAt });
82
+ return { selfRelayToken, expiresAt };
83
+ }
84
+ /** Single-use, fail-closed transfer from the authenticated Noise channel to
85
+ * the opaque relay transport. */
86
+ authenticateSelfRelay(selfRelayToken) {
87
+ if (!selfRelayToken || selfRelayToken.length > 512)
88
+ return null;
89
+ const key = digest(selfRelayToken).toString('hex');
90
+ const ticket = this.selfRelayTickets.get(key);
91
+ this.selfRelayTickets.delete(key);
92
+ if (!ticket || ticket.expiresAt <= this.now())
93
+ return null;
94
+ return { kind: 'self-relay', serviceId: ticket.serviceId };
95
+ }
65
96
  /** Called once before upgrading. A route ticket is consumed even if expired. */
66
97
  authenticate(authorization, routeToken) {
67
98
  if (authorization && routeToken)
@@ -83,6 +114,8 @@ export class RouteAccess {
83
114
  return { kind: 'client', subjectId: ticket.subjectId, serviceId: ticket.serviceId };
84
115
  }
85
116
  allowRegister(principal, serviceId) {
117
+ if (principal.kind === 'self-relay')
118
+ return principal.serviceId === serviceId;
86
119
  return principal.kind === 'relay' && this.relays().some(r => r.id === principal.id && digest(r.token).toString('hex') === principal.tokenHash && r.targets.includes(serviceId));
87
120
  }
88
121
  allowRoute(principal, serviceId) {
@@ -92,7 +125,8 @@ export class RouteAccess {
92
125
  if (!validId(serviceId))
93
126
  return false;
94
127
  const registry = this.registry();
95
- return registry.relays.some(relay => relay.targets.includes(serviceId)) || registry.directTargets.some(target => target.serviceId === serviceId) || this.additionalTargets().some(target => target.serviceId === serviceId);
128
+ return registry.relays.some(relay => relay.targets.includes(serviceId)) || registry.directTargets.some(target => target.serviceId === serviceId)
129
+ || this.additionalTargets().some(target => target.serviceId === serviceId) || this.additionalTargetIds().includes(serviceId);
96
130
  }
97
131
  addDirectTarget(target) {
98
132
  const url = new URL(target.url);
@@ -139,6 +173,9 @@ export class RouteAccess {
139
173
  url.protocol = 'https:';
140
174
  targets.set(target.serviceId, { serviceId: target.serviceId, url: url.origin, ...(target.label ? { label: target.label } : {}) });
141
175
  }
176
+ for (const serviceId of this.additionalTargetIds())
177
+ if (validId(serviceId) && !targets.has(serviceId))
178
+ targets.set(serviceId, { serviceId });
142
179
  return [...targets.values()];
143
180
  }
144
181
  }
@@ -356,6 +356,11 @@ export async function createFederationRuntime(app, directory, handlers, options
356
356
  });
357
357
  send({ type: 'result', id: packet.id, items });
358
358
  }
359
+ else if (packet.type === 'self-relay-ticket') {
360
+ if (!options.issueSelfRelayTicket)
361
+ throw new Error('SELF_RELAY_UNAVAILABLE');
362
+ send({ type: 'result', id: packet.id, serviceId: subjectId, ...options.issueSelfRelayTicket(subjectId) });
363
+ }
359
364
  else if (packet.type === 'route-invite-create') {
360
365
  check('authorization.manage');
361
366
  if (!options.createRouteInvitation || typeof packet.serviceId !== 'string' || !packet.serviceId || packet.serviceId.length > 512)
@@ -0,0 +1,182 @@
1
+ import WebSocket from 'ws';
2
+ import { desktopDirectTargets } from './desktopTargets.js';
3
+ import { RelayClient } from './relay.js';
4
+ import { readPinnedCertificateAuthority } from './trustedCa.js';
5
+ import { connectCollaborationRpc } from '../agent/collaborationPeerTransport.js';
6
+ function relayEndpoint(origin, selfRelayToken) {
7
+ const url = new URL('/api/federation/relay', origin);
8
+ url.protocol = 'wss:';
9
+ url.searchParams.set('selfRelayToken', selfRelayToken);
10
+ return url.href;
11
+ }
12
+ function secureEndpoint(origin) {
13
+ const url = new URL('/api/federation/secure', origin);
14
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
15
+ return url.href;
16
+ }
17
+ function openSocket(url, ca, maxPayload = 256 * 1024) {
18
+ return new Promise((resolve, reject) => {
19
+ const secure = new URL(url).protocol === 'wss:';
20
+ const socket = new WebSocket(url, {
21
+ ...(secure ? { ...(ca ? { ca } : {}), rejectUnauthorized: true } : {}),
22
+ followRedirects: false,
23
+ handshakeTimeout: 10_000,
24
+ maxPayload,
25
+ });
26
+ const failed = () => { cleanup(); socket.terminate(); reject(new Error('SELF_RELAY_CONNECTION_FAILED')); };
27
+ const opened = () => { cleanup(); socket.on('error', () => socket.terminate()); resolve(socket); };
28
+ const cleanup = () => { socket.off('open', opened); socket.off('error', failed); socket.off('close', failed); };
29
+ socket.once('open', opened);
30
+ socket.once('error', failed);
31
+ socket.once('close', failed);
32
+ });
33
+ }
34
+ function waitForReady(socket) {
35
+ return new Promise((resolve, reject) => {
36
+ const timer = setTimeout(() => fail(), 10_000);
37
+ timer.unref?.();
38
+ const cleanup = () => { clearTimeout(timer); socket.off('message', ready); socket.off('close', fail); };
39
+ const fail = () => { cleanup(); socket.terminate(); reject(new Error('SELF_RELAY_AUTHENTICATION_FAILED')); };
40
+ const ready = (raw, binary) => {
41
+ try {
42
+ const frame = binary ? null : JSON.parse(raw.toString());
43
+ if (frame?.type !== 'ready') {
44
+ fail();
45
+ return;
46
+ }
47
+ cleanup();
48
+ resolve();
49
+ }
50
+ catch {
51
+ fail();
52
+ }
53
+ };
54
+ socket.once('message', ready);
55
+ socket.once('close', fail);
56
+ });
57
+ }
58
+ /**
59
+ * A local Termdock service may publish only its own stable identity to other
60
+ * services pinned by the same Desktop installation. Desktop supplies addresses
61
+ * and CA pins only; the service proves itself with its own Noise identity and
62
+ * no browser/device authorization is copied into this process.
63
+ */
64
+ export class DesktopSelfRelay {
65
+ options;
66
+ states = new Map();
67
+ timer;
68
+ stopped = false;
69
+ constructor(options) {
70
+ this.options = options;
71
+ }
72
+ start() {
73
+ if (this.timer || this.stopped)
74
+ return;
75
+ this.refresh();
76
+ this.timer = setInterval(() => this.refresh(), 2_000);
77
+ this.timer.unref?.();
78
+ }
79
+ close() {
80
+ if (this.stopped)
81
+ return;
82
+ this.stopped = true;
83
+ clearInterval(this.timer);
84
+ this.timer = undefined;
85
+ for (const state of this.states.values())
86
+ this.stopState(state);
87
+ this.states.clear();
88
+ }
89
+ refresh() {
90
+ if (this.stopped)
91
+ return;
92
+ const desired = new Map(desktopDirectTargets(this.options.desktopFile, this.options.serviceId)
93
+ .map(target => [target.serviceId, target]));
94
+ for (const [serviceId, state] of this.states) {
95
+ const target = desired.get(serviceId);
96
+ const signature = target && JSON.stringify([target.url, target.caFingerprint256]);
97
+ if (!target || signature !== state.signature) {
98
+ this.stopState(state);
99
+ this.states.delete(serviceId);
100
+ }
101
+ }
102
+ for (const [serviceId, target] of desired)
103
+ if (!this.states.has(serviceId)) {
104
+ const state = { signature: JSON.stringify([target.url, target.caFingerprint256]), stopped: false };
105
+ this.states.set(serviceId, state);
106
+ void this.run(state, target);
107
+ }
108
+ }
109
+ stopState(state) {
110
+ if (state.stopped)
111
+ return;
112
+ state.stopped = true;
113
+ state.wake?.();
114
+ state.wake = undefined;
115
+ state.client?.close();
116
+ state.client = undefined;
117
+ state.upstream?.terminate();
118
+ state.upstream = undefined;
119
+ }
120
+ async run(state, target) {
121
+ let delay = 1_000;
122
+ while (!this.stopped && !state.stopped) {
123
+ try {
124
+ await this.connect(state, target);
125
+ delay = 1_000;
126
+ }
127
+ catch { /* Offline and older entries remain dormant and retry with backoff. */ }
128
+ if (this.stopped || state.stopped)
129
+ break;
130
+ await new Promise(resolve => {
131
+ const timer = setTimeout(() => { state.wake = undefined; resolve(); }, delay);
132
+ timer.unref?.();
133
+ state.wake = () => { clearTimeout(timer); state.wake = undefined; resolve(); };
134
+ });
135
+ delay = Math.min(delay * 2, 30_000);
136
+ }
137
+ }
138
+ async connect(state, target) {
139
+ const peer = { serviceId: target.serviceId, origin: target.url, ...(target.caFingerprint256 ? { caFingerprint256: target.caFingerprint256 } : {}) };
140
+ const rpc = await connectCollaborationRpc(this.options.identity, peer);
141
+ let selfRelayToken;
142
+ try {
143
+ const result = await rpc.request({ type: 'self-relay-ticket' });
144
+ if (result.serviceId !== this.options.serviceId || typeof result.selfRelayToken !== 'string'
145
+ || !/^[A-Za-z0-9_-]{43}$/.test(result.selfRelayToken))
146
+ throw new Error('INVALID_SELF_RELAY_TICKET');
147
+ selfRelayToken = result.selfRelayToken;
148
+ }
149
+ finally {
150
+ rpc.close();
151
+ }
152
+ if (this.stopped || state.stopped)
153
+ return;
154
+ const ca = target.caFingerprint256 ? await readPinnedCertificateAuthority(target.url, target.caFingerprint256) : undefined;
155
+ const upstream = await openSocket(relayEndpoint(target.url, selfRelayToken), ca);
156
+ state.upstream = upstream;
157
+ try {
158
+ if (this.stopped || state.stopped)
159
+ return;
160
+ await waitForReady(upstream);
161
+ if (this.stopped || state.stopped)
162
+ return;
163
+ const client = new RelayClient(upstream, { targets: new Map([[this.options.serviceId,
164
+ () => openSocket(secureEndpoint(this.options.localOrigin), this.options.localCa, 1024 * 1024)]]) });
165
+ state.client = client;
166
+ await new Promise(resolve => {
167
+ if (upstream.readyState !== WebSocket.OPEN)
168
+ resolve();
169
+ else
170
+ upstream.once('close', () => resolve());
171
+ });
172
+ }
173
+ finally {
174
+ state.client?.close();
175
+ state.client = undefined;
176
+ if (upstream.readyState !== WebSocket.CLOSED)
177
+ upstream.terminate();
178
+ if (state.upstream === upstream)
179
+ state.upstream = undefined;
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,12 @@
1
+ import { X509Certificate } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { isIP } from 'node:net';
4
+ // Use Node's certificate parser: the system openssl (notably LibreSSL on
5
+ // macOS) may not support `x509 -ext`. Propagate read/parse errors so callers
6
+ // cannot mistake an inspection failure for an empty SAN extension.
7
+ export async function getMissingCertificateNames(certPath, required) {
8
+ const cert = new X509Certificate(await readFile(certPath));
9
+ return required.filter((name) => isIP(name)
10
+ ? cert.checkIP(name) === undefined
11
+ : cert.checkHost(name, { subject: 'never', wildcards: false }) === undefined);
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termdock",
3
- "version": "1.4.239",
3
+ "version": "1.4.241",
4
4
  "description": "A complete web-based terminal application with modern UI",
5
5
  "type": "module",
6
6
  "main": ".desktop-dist/main.js",
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "packageName": "termdock",
4
- "version": "1.4.239",
4
+ "version": "1.4.241",
5
5
  "runtimeProtocolVersion": 1,
6
6
  "minimumDesktopVersion": "1.4.46",
7
7
  "nodeMajor": 22,
8
8
  "dependencyHash": "sha256-K4KyYJuVoQDpZsBM8ZX1Rw3MddOxAzVdp/cPm0iGZz0=",
9
- "serverBundleHash": "sha256-b2hW5XzG58ErGFDet7CETfl7sHtPoDhHjTnL3mzU63s=",
10
- "clientBundleHash": "sha256-1wPbozETRlatzFMaBkX5wcha5zbSwUdAw4I0XckzdLk=",
9
+ "serverBundleHash": "sha256-pd6lxkGXO9l4hUWug+Q8/kxaLYDXfSgODkoynCdwgBI=",
10
+ "clientBundleHash": "sha256-zyDixEKnPdU8PZdH7+xvwW32LWLFTq5o2zdqqOr3AJM=",
11
11
  "entrypoint": "dist/server/cli.js",
12
12
  "clientEntrypoint": "dist/client/index.html"
13
13
  }