termdock 1.4.350 → 1.4.352

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 (448) hide show
  1. package/dist/client/assets/AndroidMirrorView-DkOZxYLg.js +3 -0
  2. package/dist/client/assets/AndroidMirrorView-DkOZxYLg.js.br +0 -0
  3. package/dist/client/assets/AndroidMirrorView-DkOZxYLg.js.gz +0 -0
  4. package/dist/client/assets/App-DqRkfVSm.js +178 -0
  5. package/dist/client/assets/App-DqRkfVSm.js.br +0 -0
  6. package/dist/client/assets/App-DqRkfVSm.js.gz +0 -0
  7. package/dist/client/assets/ArchitecturePanel-m9GKl3cl.js +9 -0
  8. package/dist/client/assets/ArchitecturePanel-m9GKl3cl.js.br +0 -0
  9. package/dist/client/assets/ArchitecturePanel-m9GKl3cl.js.gz +0 -0
  10. package/dist/client/assets/ChangeWalkthroughPanel-HZ0zvFvi.js +2 -0
  11. package/dist/client/assets/ChangeWalkthroughPanel-HZ0zvFvi.js.br +0 -0
  12. package/dist/client/assets/ChangeWalkthroughPanel-HZ0zvFvi.js.gz +0 -0
  13. package/dist/client/assets/CollaborationMainWorkspace-B4OMk92m.js +1 -0
  14. package/dist/client/assets/CollaborationMainWorkspace-B4OMk92m.js.br +0 -0
  15. package/dist/client/assets/CollaborationMainWorkspace-B4OMk92m.js.gz +0 -0
  16. package/dist/client/assets/CollaborationTaskContent-Cbg3iH6E.js +2 -0
  17. package/dist/client/assets/ComputerControlView-CSs54lf0.js +6 -0
  18. package/dist/client/assets/ComputerControlView-CSs54lf0.js.br +0 -0
  19. package/dist/client/assets/ComputerControlView-CSs54lf0.js.gz +0 -0
  20. package/dist/client/assets/DagPlayground-yoS2llnB.js +1 -0
  21. package/dist/client/assets/DagPlayground-yoS2llnB.js.br +0 -0
  22. package/dist/client/assets/DagPlayground-yoS2llnB.js.gz +0 -0
  23. package/dist/client/assets/{DiffLab-DEw4y4Hk.js → DiffLab-Dj1MT9pY.js} +2 -2
  24. package/dist/client/assets/DiffLab-Dj1MT9pY.js.br +0 -0
  25. package/dist/client/assets/DiffLab-Dj1MT9pY.js.gz +0 -0
  26. package/dist/client/assets/DiffReview-BX2S9hNp.js +1 -0
  27. package/dist/client/assets/DiffReview-BX2S9hNp.js.br +0 -0
  28. package/dist/client/assets/DiffReview-BX2S9hNp.js.gz +0 -0
  29. package/dist/client/assets/{DiffReviewLab-CDXsy0jE.js → DiffReviewLab-DqI14GEk.js} +2 -2
  30. package/dist/client/assets/DiffReviewLab-DqI14GEk.js.br +0 -0
  31. package/dist/client/assets/DiffReviewLab-DqI14GEk.js.gz +0 -0
  32. package/dist/client/assets/DiffViewer-AZvdDGfH.js +34 -0
  33. package/dist/client/assets/DiffViewer-AZvdDGfH.js.br +0 -0
  34. package/dist/client/assets/DiffViewer-AZvdDGfH.js.gz +0 -0
  35. package/dist/client/assets/GitLoadingSkeleton-CBPykkZP.js +1 -0
  36. package/dist/client/assets/GitLoadingSkeleton-CBPykkZP.js.br +0 -0
  37. package/dist/client/assets/GitLoadingSkeleton-CBPykkZP.js.gz +0 -0
  38. package/dist/client/assets/MarkdownPreview-DpvTnKkj.js +19 -0
  39. package/dist/client/assets/MarkdownPreview-DpvTnKkj.js.br +0 -0
  40. package/dist/client/assets/MarkdownPreview-DpvTnKkj.js.gz +0 -0
  41. package/dist/client/assets/{ModelPreview-BEiOgE0G.js → ModelPreview-Du_AyPr9.js} +166 -166
  42. package/dist/client/assets/ModelPreview-Du_AyPr9.js.br +0 -0
  43. package/dist/client/assets/ModelPreview-Du_AyPr9.js.gz +0 -0
  44. package/dist/client/assets/RecordingSaveDialog-CtXaGgIo.js +1 -0
  45. package/dist/client/assets/RecordingSaveDialog-CtXaGgIo.js.br +0 -0
  46. package/dist/client/assets/RecordingSaveDialog-CtXaGgIo.js.gz +0 -0
  47. package/dist/client/assets/RightSidebar-DU3gwdzg.js +1662 -0
  48. package/dist/client/assets/RightSidebar-DU3gwdzg.js.br +0 -0
  49. package/dist/client/assets/RightSidebar-DU3gwdzg.js.gz +0 -0
  50. package/dist/client/assets/SessionChatView-CxfwJMuK.js +13 -0
  51. package/dist/client/assets/SessionChatView-CxfwJMuK.js.br +0 -0
  52. package/dist/client/assets/SessionChatView-CxfwJMuK.js.gz +0 -0
  53. package/dist/client/assets/SessionChatView-D8GoESjb.css +1 -0
  54. package/dist/client/assets/SessionChatView-D8GoESjb.css.br +0 -0
  55. package/dist/client/assets/SessionChatView-D8GoESjb.css.gz +0 -0
  56. package/dist/client/assets/SessionResourceSidebar-B31PU57J.js +2 -0
  57. package/dist/client/assets/SessionResourceSidebar-B31PU57J.js.br +0 -0
  58. package/dist/client/assets/SessionResourceSidebar-B31PU57J.js.gz +0 -0
  59. package/dist/client/assets/TodoWorkspace-j9BLgk94.js +1 -0
  60. package/dist/client/assets/TodoWorkspace-j9BLgk94.js.br +0 -0
  61. package/dist/client/assets/TodoWorkspace-j9BLgk94.js.gz +0 -0
  62. package/dist/client/assets/{abnfDiagram-VCTEODGH-ByzlUu_B.js → abnfDiagram-VCTEODGH-LfmMIpPX.js} +1 -1
  63. package/dist/client/assets/abnfDiagram-VCTEODGH-LfmMIpPX.js.br +0 -0
  64. package/dist/client/assets/abnfDiagram-VCTEODGH-LfmMIpPX.js.gz +0 -0
  65. package/dist/client/assets/app-BMJesFDU.js +36 -0
  66. package/dist/client/assets/app-BMJesFDU.js.br +0 -0
  67. package/dist/client/assets/app-BMJesFDU.js.gz +0 -0
  68. package/dist/client/assets/{arc-BA6JM6vl.js → arc-DMzcmH2U.js} +1 -1
  69. package/dist/client/assets/arc-DMzcmH2U.js.br +0 -0
  70. package/dist/client/assets/arc-DMzcmH2U.js.gz +0 -0
  71. package/dist/client/assets/{architectureDiagram-5GKGNRK7-hCHxOfKw.js → architectureDiagram-5GKGNRK7-CphS_S5Q.js} +1 -1
  72. package/dist/client/assets/architectureDiagram-5GKGNRK7-CphS_S5Q.js.br +0 -0
  73. package/dist/client/assets/{architectureDiagram-5GKGNRK7-hCHxOfKw.js.gz → architectureDiagram-5GKGNRK7-CphS_S5Q.js.gz} +0 -0
  74. package/dist/client/assets/arrow-down-D-WZS308.js +1 -0
  75. package/dist/client/assets/arrow-up-CelbB9FE.js +1 -0
  76. package/dist/client/assets/{blockDiagram-I7D4REHJ-OmEN9hUB.js → blockDiagram-I7D4REHJ-Dx_4qkcL.js} +1 -1
  77. package/dist/client/assets/blockDiagram-I7D4REHJ-Dx_4qkcL.js.br +0 -0
  78. package/dist/client/assets/blockDiagram-I7D4REHJ-Dx_4qkcL.js.gz +0 -0
  79. package/dist/client/assets/{c4Diagram-7LVT6UL2-DfLTv8zh.js → c4Diagram-7LVT6UL2-DiGdnPo4.js} +1 -1
  80. package/dist/client/assets/c4Diagram-7LVT6UL2-DiGdnPo4.js.br +0 -0
  81. package/dist/client/assets/c4Diagram-7LVT6UL2-DiGdnPo4.js.gz +0 -0
  82. package/dist/client/assets/channel-BxAJ9Qmh.js +1 -0
  83. package/dist/client/assets/chevron-left-5sk7gL3u.js +1 -0
  84. package/dist/client/assets/{chunk-2Q5K7J3B-C3O76PNZ.js → chunk-2Q5K7J3B-M50KNmR8.js} +1 -1
  85. package/dist/client/assets/{chunk-5VM5RSS4-CsWZd30k.js → chunk-5VM5RSS4-wXVM-Max.js} +1 -1
  86. package/dist/client/assets/{chunk-F27PBJKO-yxCv2Uqk.js → chunk-F27PBJKO-BfQILS9u.js} +1 -1
  87. package/dist/client/assets/chunk-F27PBJKO-BfQILS9u.js.br +0 -0
  88. package/dist/client/assets/chunk-F27PBJKO-BfQILS9u.js.gz +0 -0
  89. package/dist/client/assets/{chunk-IMKFNOWR-CdPjlb9f.js → chunk-IMKFNOWR-D111Jpl3.js} +1 -1
  90. package/dist/client/assets/chunk-IMKFNOWR-D111Jpl3.js.br +0 -0
  91. package/dist/client/assets/chunk-IMKFNOWR-D111Jpl3.js.gz +0 -0
  92. package/dist/client/assets/{chunk-JWPE2WC7-BiiOLq2R.js → chunk-JWPE2WC7-BBDyly5c.js} +1 -1
  93. package/dist/client/assets/{chunk-POPQ4Y6H-BOu_1Brt.js → chunk-POPQ4Y6H-BaI-_yc7.js} +1 -1
  94. package/dist/client/assets/{chunk-SVP7TREG-D1tOJung.js → chunk-SVP7TREG-BvQafCMJ.js} +1 -1
  95. package/dist/client/assets/chunk-SVP7TREG-BvQafCMJ.js.br +0 -0
  96. package/dist/client/assets/chunk-SVP7TREG-BvQafCMJ.js.gz +0 -0
  97. package/dist/client/assets/{chunk-TICWLB2K-DZjDfYNw.js → chunk-TICWLB2K-CQXPwGBz.js} +1 -1
  98. package/dist/client/assets/chunk-TICWLB2K-CQXPwGBz.js.br +0 -0
  99. package/dist/client/assets/chunk-TICWLB2K-CQXPwGBz.js.gz +0 -0
  100. package/dist/client/assets/{chunk-XXDRQBXY-CtGPyXWh.js → chunk-XXDRQBXY-CmwZc4xV.js} +1 -1
  101. package/dist/client/assets/classDiagram-ZZMXUADV-D0oXVFxd.js +1 -0
  102. package/dist/client/assets/classDiagram-v2-VYDZK3BY-D0oXVFxd.js +1 -0
  103. package/dist/client/assets/clipboard-uSv3AiJM.js +1 -0
  104. package/dist/client/assets/clipboardText-5puxqqm_.js +1 -0
  105. package/dist/client/assets/clipboardText-5puxqqm_.js.br +0 -0
  106. package/dist/client/assets/clipboardText-5puxqqm_.js.gz +0 -0
  107. package/dist/client/assets/{cose-bilkent-JH36ORCC-C4Wb5lqD.js → cose-bilkent-JH36ORCC-CqWarkUj.js} +1 -1
  108. package/dist/client/assets/cose-bilkent-JH36ORCC-CqWarkUj.js.br +0 -0
  109. package/dist/client/assets/cose-bilkent-JH36ORCC-CqWarkUj.js.gz +0 -0
  110. package/dist/client/assets/{cynefin-OW5HDTMX-BvWKB1Xk.js → cynefin-OW5HDTMX-CFW2ifig.js} +1 -1
  111. package/dist/client/assets/cynefin-OW5HDTMX-CFW2ifig.js.br +0 -0
  112. package/dist/client/assets/cynefin-OW5HDTMX-CFW2ifig.js.gz +0 -0
  113. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-7tAkHD2P.js → cynefinDiagram-5FMLGOSQ-yggxM55S.js} +1 -1
  114. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-yggxM55S.js.br +0 -0
  115. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-yggxM55S.js.gz +0 -0
  116. package/dist/client/assets/{dagre-GXQ25YYZ-Bs6IPwks.js → dagre-GXQ25YYZ-inq7o--x.js} +1 -1
  117. package/dist/client/assets/dagre-GXQ25YYZ-inq7o--x.js.br +0 -0
  118. package/dist/client/assets/dagre-GXQ25YYZ-inq7o--x.js.gz +0 -0
  119. package/dist/client/assets/{diagram-S7CK7UJ4-CmYXaTya.js → diagram-S7CK7UJ4-BUXMnyQN.js} +1 -1
  120. package/dist/client/assets/diagram-S7CK7UJ4-BUXMnyQN.js.br +0 -0
  121. package/dist/client/assets/diagram-S7CK7UJ4-BUXMnyQN.js.gz +0 -0
  122. package/dist/client/assets/{diagram-UQ7AKVKN-CqxoefB7.js → diagram-UQ7AKVKN-BJbw-Imf.js} +1 -1
  123. package/dist/client/assets/diagram-UQ7AKVKN-BJbw-Imf.js.br +0 -0
  124. package/dist/client/assets/diagram-UQ7AKVKN-BJbw-Imf.js.gz +0 -0
  125. package/dist/client/assets/{diagram-VSXAHHWV-Dxp1MKPT.js → diagram-VSXAHHWV-CSfl8yS7.js} +1 -1
  126. package/dist/client/assets/diagram-VSXAHHWV-CSfl8yS7.js.br +0 -0
  127. package/dist/client/assets/diagram-VSXAHHWV-CSfl8yS7.js.gz +0 -0
  128. package/dist/client/assets/{diagram-VX7I27RA-BKbWvvEr.js → diagram-VX7I27RA-zV-Vrv89.js} +1 -1
  129. package/dist/client/assets/diagram-VX7I27RA-zV-Vrv89.js.br +0 -0
  130. package/dist/client/assets/{diagram-VX7I27RA-BKbWvvEr.js.gz → diagram-VX7I27RA-zV-Vrv89.js.gz} +0 -0
  131. package/dist/client/assets/{diagram-Z3DM3KII-BZnNaP7F.js → diagram-Z3DM3KII-CRnE-snw.js} +1 -1
  132. package/dist/client/assets/diagram-Z3DM3KII-CRnE-snw.js.br +0 -0
  133. package/dist/client/assets/diagram-Z3DM3KII-CRnE-snw.js.gz +0 -0
  134. package/dist/client/assets/{ebnfDiagram-PWID7BFC-BXVlN_P7.js → ebnfDiagram-PWID7BFC-CE30oImc.js} +1 -1
  135. package/dist/client/assets/ebnfDiagram-PWID7BFC-CE30oImc.js.br +0 -0
  136. package/dist/client/assets/ebnfDiagram-PWID7BFC-CE30oImc.js.gz +0 -0
  137. package/dist/client/assets/{erDiagram-RLTQ6QDP-CEdYDZU7.js → erDiagram-RLTQ6QDP-D-5bIySI.js} +1 -1
  138. package/dist/client/assets/erDiagram-RLTQ6QDP-D-5bIySI.js.br +0 -0
  139. package/dist/client/assets/erDiagram-RLTQ6QDP-D-5bIySI.js.gz +0 -0
  140. package/dist/client/assets/{flowDiagram-HODETNUW-Bbw8GPCZ.js → flowDiagram-HODETNUW-E_jPKtQR.js} +1 -1
  141. package/dist/client/assets/flowDiagram-HODETNUW-E_jPKtQR.js.br +0 -0
  142. package/dist/client/assets/flowDiagram-HODETNUW-E_jPKtQR.js.gz +0 -0
  143. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CcFbtP2y.js → ganttDiagram-EL5Y4UJY-BWvLOUzC.js} +1 -1
  144. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BWvLOUzC.js.br +0 -0
  145. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BWvLOUzC.js.gz +0 -0
  146. package/dist/client/assets/gestureArbiter-DszagC-d.js +1 -0
  147. package/dist/client/assets/gestureArbiter-DszagC-d.js.br +0 -0
  148. package/dist/client/assets/gestureArbiter-DszagC-d.js.gz +0 -0
  149. package/dist/client/assets/git-compare-BlcHEuBI.js +1 -0
  150. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-Eiust154.js → gitGraphDiagram-WWUBYQGX-nOlVCBmL.js} +1 -1
  151. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-nOlVCBmL.js.br +0 -0
  152. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-nOlVCBmL.js.gz +0 -0
  153. package/dist/client/assets/index-CA1fR_jI.css +1 -0
  154. package/dist/client/assets/index-CA1fR_jI.css.br +0 -0
  155. package/dist/client/assets/index-CA1fR_jI.css.gz +0 -0
  156. package/dist/client/assets/{infoDiagram-27XIBGKW-DUmaNZJW.js → infoDiagram-27XIBGKW-CjVm9Lzz.js} +1 -1
  157. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-qTwgXfx_.js → ishikawaDiagram-5VMMS53U-DmVMaGVp.js} +1 -1
  158. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-qTwgXfx_.js.br → ishikawaDiagram-5VMMS53U-DmVMaGVp.js.br} +0 -0
  159. package/dist/client/assets/ishikawaDiagram-5VMMS53U-DmVMaGVp.js.gz +0 -0
  160. package/dist/client/assets/{journeyDiagram-3NMN7TZE-DKy1msWo.js → journeyDiagram-3NMN7TZE-DLwkoAy9.js} +1 -1
  161. package/dist/client/assets/journeyDiagram-3NMN7TZE-DLwkoAy9.js.br +0 -0
  162. package/dist/client/assets/journeyDiagram-3NMN7TZE-DLwkoAy9.js.gz +0 -0
  163. package/dist/client/assets/{kanban-definition-UXKFOSKX-BxibybDd.js → kanban-definition-UXKFOSKX-GWkp_QiO.js} +1 -1
  164. package/dist/client/assets/kanban-definition-UXKFOSKX-GWkp_QiO.js.br +0 -0
  165. package/dist/client/assets/kanban-definition-UXKFOSKX-GWkp_QiO.js.gz +0 -0
  166. package/dist/client/assets/{layout-Bh6fKHRM.js → layout-DFg5a61y.js} +1 -1
  167. package/dist/client/assets/layout-DFg5a61y.js.br +0 -0
  168. package/dist/client/assets/layout-DFg5a61y.js.gz +0 -0
  169. package/dist/client/assets/{linear-SRlL3VWX.js → linear-B0OdJLe1.js} +1 -1
  170. package/dist/client/assets/{linear-SRlL3VWX.js.br → linear-B0OdJLe1.js.br} +0 -0
  171. package/dist/client/assets/linear-B0OdJLe1.js.gz +0 -0
  172. package/dist/client/assets/link-BnxdKJ7b.js +8 -0
  173. package/dist/client/assets/link-BnxdKJ7b.js.br +0 -0
  174. package/dist/client/assets/link-BnxdKJ7b.js.gz +0 -0
  175. package/dist/client/assets/list-tree-Bou8VSzi.js +1 -0
  176. package/dist/client/assets/loader-circle-q7h8uwFI.js +1 -0
  177. package/dist/client/assets/maximize-2-BGTBw-Ku.js +1 -0
  178. package/dist/client/assets/{mermaid.core-D0bcNdw3.js → mermaid.core-DQA3ykXv.js} +5 -5
  179. package/dist/client/assets/mermaid.core-DQA3ykXv.js.br +0 -0
  180. package/dist/client/assets/mermaid.core-DQA3ykXv.js.gz +0 -0
  181. package/dist/client/assets/{mindmap-definition-YA3MSWOX-CImZ_aI0.js → mindmap-definition-YA3MSWOX-DsZAVAGP.js} +2 -2
  182. package/dist/client/assets/mindmap-definition-YA3MSWOX-DsZAVAGP.js.br +0 -0
  183. package/dist/client/assets/mindmap-definition-YA3MSWOX-DsZAVAGP.js.gz +0 -0
  184. package/dist/client/assets/passwordBootstrap-CEZSL94g.js +2 -0
  185. package/dist/client/assets/passwordBootstrap-CEZSL94g.js.br +0 -0
  186. package/dist/client/assets/passwordBootstrap-CEZSL94g.js.gz +0 -0
  187. package/dist/client/assets/{pegDiagram-XKGWAZYB-BT4pTf8O.js → pegDiagram-XKGWAZYB-C3ar01Lf.js} +1 -1
  188. package/dist/client/assets/pegDiagram-XKGWAZYB-C3ar01Lf.js.br +0 -0
  189. package/dist/client/assets/pegDiagram-XKGWAZYB-C3ar01Lf.js.gz +0 -0
  190. package/dist/client/assets/{pieDiagram-E7YTZNPT-C5K58ien.js → pieDiagram-E7YTZNPT-C8LIa9gE.js} +1 -1
  191. package/dist/client/assets/pieDiagram-E7YTZNPT-C8LIa9gE.js.br +0 -0
  192. package/dist/client/assets/pieDiagram-E7YTZNPT-C8LIa9gE.js.gz +0 -0
  193. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-DzSugYXz.js → quadrantDiagram-AXDQQJYC-BuRJ1f9F.js} +1 -1
  194. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BuRJ1f9F.js.br +0 -0
  195. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BuRJ1f9F.js.gz +0 -0
  196. package/dist/client/assets/quote-qXNI_NTi.js +1 -0
  197. package/dist/client/assets/{railroadDiagram-O6MQD6OU-BY07SouQ.js → railroadDiagram-O6MQD6OU-D7iZy3dO.js} +1 -1
  198. package/dist/client/assets/railroadDiagram-O6MQD6OU-D7iZy3dO.js.br +0 -0
  199. package/dist/client/assets/railroadDiagram-O6MQD6OU-D7iZy3dO.js.gz +0 -0
  200. package/dist/client/assets/rdpSession-DUBlG26K.js +1 -0
  201. package/dist/client/assets/rdpSession-DUBlG26K.js.br +0 -0
  202. package/dist/client/assets/rdpSession-DUBlG26K.js.gz +0 -0
  203. package/dist/client/assets/{requirementDiagram-BXWQKSXE-C7WieVuE.js → requirementDiagram-BXWQKSXE-DhY8g8qK.js} +1 -1
  204. package/dist/client/assets/requirementDiagram-BXWQKSXE-DhY8g8qK.js.br +0 -0
  205. package/dist/client/assets/requirementDiagram-BXWQKSXE-DhY8g8qK.js.gz +0 -0
  206. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-BJsDWNs4.js → sankeyDiagram-P5KCCOFB-DDA_LQcg.js} +1 -1
  207. package/dist/client/assets/sankeyDiagram-P5KCCOFB-DDA_LQcg.js.br +0 -0
  208. package/dist/client/assets/sankeyDiagram-P5KCCOFB-DDA_LQcg.js.gz +0 -0
  209. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BeJ_7853.js → sequenceDiagram-WJ2MYXX4-bWgfdfwY.js} +1 -1
  210. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BeJ_7853.js.br → sequenceDiagram-WJ2MYXX4-bWgfdfwY.js.br} +0 -0
  211. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-bWgfdfwY.js.gz +0 -0
  212. package/dist/client/assets/settings-2-B-sr3wQM.js +1 -0
  213. package/dist/client/assets/{sizeCapture-INFHLROL-DOsjXKPV.js → sizeCapture-INFHLROL-BYxi7e0R.js} +1 -1
  214. package/dist/client/assets/sizeCapture-INFHLROL-BYxi7e0R.js.br +0 -0
  215. package/dist/client/assets/sizeCapture-INFHLROL-BYxi7e0R.js.gz +0 -0
  216. package/dist/client/assets/{sparkles-e9SkpP7a.js → sparkles-CCLflSFy.js} +1 -1
  217. package/dist/client/assets/{stateDiagram-D77RDMKH-6s709_RT.js → stateDiagram-D77RDMKH-6vqC2h36.js} +1 -1
  218. package/dist/client/assets/stateDiagram-D77RDMKH-6vqC2h36.js.br +0 -0
  219. package/dist/client/assets/stateDiagram-D77RDMKH-6vqC2h36.js.gz +0 -0
  220. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-Bb23lB9W.js +1 -0
  221. package/dist/client/assets/sun-B9_TfxlA.js +1 -0
  222. package/dist/client/assets/{swimlanes-42K2YHIH-5R5dw9_R.js → swimlanes-42K2YHIH-B4qrXam7.js} +1 -1
  223. package/dist/client/assets/swimlanes-42K2YHIH-B4qrXam7.js.br +0 -0
  224. package/dist/client/assets/swimlanes-42K2YHIH-B4qrXam7.js.gz +0 -0
  225. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CQ1XJUVU.js +8 -0
  226. package/dist/client/assets/swiper-DJjq1TYT.js +1 -0
  227. package/dist/client/assets/swiper-DJjq1TYT.js.br +0 -0
  228. package/dist/client/assets/swiper-DJjq1TYT.js.gz +0 -0
  229. package/dist/client/assets/syntaxHighlight-bcOB6oww.js +4 -0
  230. package/dist/client/assets/syntaxHighlight-bcOB6oww.js.br +0 -0
  231. package/dist/client/assets/syntaxHighlight-bcOB6oww.js.gz +0 -0
  232. package/dist/client/assets/{timeline-definition-24CTP7MA-B4YN0lsx.js → timeline-definition-24CTP7MA-D0_004MF.js} +1 -1
  233. package/dist/client/assets/timeline-definition-24CTP7MA-D0_004MF.js.br +0 -0
  234. package/dist/client/assets/timeline-definition-24CTP7MA-D0_004MF.js.gz +0 -0
  235. package/dist/client/assets/triangle-alert-D3w7mA17.js +1 -0
  236. package/dist/client/assets/unplug-BSblgecr.js +1 -0
  237. package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js +1 -0
  238. package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js.br +0 -0
  239. package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js.gz +0 -0
  240. package/dist/client/assets/v4-EwEgHOG0.js +1 -0
  241. package/dist/client/assets/{vennDiagram-4TSXK5OY-Wd83SmWI.js → vennDiagram-4TSXK5OY-Bqrx-47p.js} +1 -1
  242. package/dist/client/assets/vennDiagram-4TSXK5OY-Bqrx-47p.js.br +0 -0
  243. package/dist/client/assets/vennDiagram-4TSXK5OY-Bqrx-47p.js.gz +0 -0
  244. package/dist/client/assets/videoCompression-df1Ej3AR.js +3 -0
  245. package/dist/client/assets/videoCompression-df1Ej3AR.js.br +0 -0
  246. package/dist/client/assets/videoCompression-df1Ej3AR.js.gz +0 -0
  247. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-CGsYvqWN.js → wardleyDiagram-VM6X3IG4-C4LCUdfd.js} +1 -1
  248. package/dist/client/assets/wardleyDiagram-VM6X3IG4-C4LCUdfd.js.br +0 -0
  249. package/dist/client/assets/wardleyDiagram-VM6X3IG4-C4LCUdfd.js.gz +0 -0
  250. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-ednJq1_u.js → xychartDiagram-S5SC5T6Z-E5AQcZVI.js} +1 -1
  251. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-ednJq1_u.js.br → xychartDiagram-S5SC5T6Z-E5AQcZVI.js.br} +0 -0
  252. package/dist/client/assets/xychartDiagram-S5SC5T6Z-E5AQcZVI.js.gz +0 -0
  253. package/dist/client/index.html +2 -2
  254. package/dist/client/index.html.br +0 -0
  255. package/dist/client/index.html.gz +0 -0
  256. package/dist/client/sw-encrypted-background.js +1 -1
  257. package/dist/client/sw-encrypted-background.js.br +0 -0
  258. package/dist/client/sw-encrypted-background.js.gz +0 -0
  259. package/dist/client/sw.js +1 -1
  260. package/dist/client/sw.js.br +0 -0
  261. package/dist/client/sw.js.gz +0 -0
  262. package/dist/server/agent/collaborationDeliveryWorker.js +11 -0
  263. package/dist/server/agent/collaborationPrompt.js +4 -2
  264. package/dist/server/agent/collaborationRoutes.js +2 -0
  265. package/dist/server/agent/collaborationStore.js +7 -1
  266. package/dist/server/agent/sessionAttachmentRoutes.js +39 -0
  267. package/dist/server/agent/sessionChatRoutes.js +64 -0
  268. package/dist/server/agent/sessionViewModeRoutes.js +72 -0
  269. package/dist/server/android/recording.js +19 -17
  270. package/dist/server/cli.js +34 -0
  271. package/dist/server/entry.js +26 -17
  272. package/dist/server/federation/accessPolicy.js +8 -3
  273. package/dist/server/federation/authorization.js +26 -3
  274. package/dist/server/federation/invitations.js +27 -6
  275. package/dist/server/federation/relay.js +21 -3
  276. package/dist/server/federation/requestContext.js +5 -1
  277. package/dist/server/federation/resourceAccess.js +63 -0
  278. package/dist/server/federation/runtime.js +67 -18
  279. package/dist/server/federation/sessionSharing.js +39 -0
  280. package/dist/server/federation/socketDuplex.js +3 -3
  281. package/dist/server/routes/android.js +36 -7
  282. package/dist/server/routes/computer.js +14 -4
  283. package/dist/server/routes/filesystem.js +6 -3
  284. package/dist/server/routes/rdp.js +4 -3
  285. package/dist/server/routes/sharedFiles.js +51 -0
  286. package/dist/server/routes/terminal.js +83 -14
  287. package/dist/server/routes/vncReadOnly.js +134 -0
  288. package/dist/server/shared/shareResources.js +37 -0
  289. package/dist/server/todos/cli.js +91 -0
  290. package/dist/server/todos/routes.js +73 -0
  291. package/dist/server/todos/store.js +240 -0
  292. package/dist/server/todos/types.js +1 -0
  293. package/dist/server/utils/autoUpdate.js +7 -2
  294. package/dist/server/utils/clientSnapshotMaintenance.js +200 -0
  295. package/dist/server/utils/runtimeClient.js +79 -0
  296. package/dist/server/utils/serverCleanup.js +24 -0
  297. package/dist/server/utils/storageMaintenance.js +60 -0
  298. package/dist/server/utils/version.js +2 -0
  299. package/dist/server/utils/workspaceCacheMaintenance.js +158 -0
  300. package/docs/integrations.md +23 -0
  301. package/package.json +1 -1
  302. package/runtime-manifest.json +3 -3
  303. package/dist/client/assets/App-TPJoJbGD.js +0 -187
  304. package/dist/client/assets/App-TPJoJbGD.js.br +0 -0
  305. package/dist/client/assets/App-TPJoJbGD.js.gz +0 -0
  306. package/dist/client/assets/ArchitecturePanel-DzVy71gM.js +0 -9
  307. package/dist/client/assets/ArchitecturePanel-DzVy71gM.js.br +0 -0
  308. package/dist/client/assets/ArchitecturePanel-DzVy71gM.js.gz +0 -0
  309. package/dist/client/assets/ChangeWalkthroughPanel-DN7BCzqC.js +0 -2
  310. package/dist/client/assets/ChangeWalkthroughPanel-DN7BCzqC.js.br +0 -0
  311. package/dist/client/assets/ChangeWalkthroughPanel-DN7BCzqC.js.gz +0 -0
  312. package/dist/client/assets/CollaborationMainWorkspace-B1PZuCC6.js +0 -1
  313. package/dist/client/assets/CollaborationMainWorkspace-B1PZuCC6.js.br +0 -0
  314. package/dist/client/assets/CollaborationMainWorkspace-B1PZuCC6.js.gz +0 -0
  315. package/dist/client/assets/CollaborationTaskContent-CG7g1LMH.js +0 -2
  316. package/dist/client/assets/ComputerControlView-BlI9Ai_k.js +0 -6
  317. package/dist/client/assets/ComputerControlView-BlI9Ai_k.js.br +0 -0
  318. package/dist/client/assets/ComputerControlView-BlI9Ai_k.js.gz +0 -0
  319. package/dist/client/assets/DagPlayground-V0hTKB-S.js +0 -1
  320. package/dist/client/assets/DagPlayground-V0hTKB-S.js.br +0 -0
  321. package/dist/client/assets/DagPlayground-V0hTKB-S.js.gz +0 -0
  322. package/dist/client/assets/DiffLab-DEw4y4Hk.js.br +0 -0
  323. package/dist/client/assets/DiffLab-DEw4y4Hk.js.gz +0 -0
  324. package/dist/client/assets/DiffReview-Cnnt3TMK.js +0 -1
  325. package/dist/client/assets/DiffReview-Cnnt3TMK.js.br +0 -0
  326. package/dist/client/assets/DiffReview-Cnnt3TMK.js.gz +0 -0
  327. package/dist/client/assets/DiffReviewLab-CDXsy0jE.js.br +0 -0
  328. package/dist/client/assets/DiffReviewLab-CDXsy0jE.js.gz +0 -0
  329. package/dist/client/assets/DiffViewer-Dh40XiCM.js +0 -37
  330. package/dist/client/assets/DiffViewer-Dh40XiCM.js.br +0 -0
  331. package/dist/client/assets/DiffViewer-Dh40XiCM.js.gz +0 -0
  332. package/dist/client/assets/ModelPreview-BEiOgE0G.js.br +0 -0
  333. package/dist/client/assets/ModelPreview-BEiOgE0G.js.gz +0 -0
  334. package/dist/client/assets/RightSidebar-DiECnX5D.js +0 -1679
  335. package/dist/client/assets/RightSidebar-DiECnX5D.js.br +0 -0
  336. package/dist/client/assets/RightSidebar-DiECnX5D.js.gz +0 -0
  337. package/dist/client/assets/abnfDiagram-VCTEODGH-ByzlUu_B.js.br +0 -0
  338. package/dist/client/assets/abnfDiagram-VCTEODGH-ByzlUu_B.js.gz +0 -0
  339. package/dist/client/assets/app-CKAFlgMD.js +0 -35
  340. package/dist/client/assets/app-CKAFlgMD.js.br +0 -0
  341. package/dist/client/assets/app-CKAFlgMD.js.gz +0 -0
  342. package/dist/client/assets/arc-BA6JM6vl.js.br +0 -0
  343. package/dist/client/assets/arc-BA6JM6vl.js.gz +0 -0
  344. package/dist/client/assets/architectureDiagram-5GKGNRK7-hCHxOfKw.js.br +0 -0
  345. package/dist/client/assets/blockDiagram-I7D4REHJ-OmEN9hUB.js.br +0 -0
  346. package/dist/client/assets/blockDiagram-I7D4REHJ-OmEN9hUB.js.gz +0 -0
  347. package/dist/client/assets/c4Diagram-7LVT6UL2-DfLTv8zh.js.br +0 -0
  348. package/dist/client/assets/c4Diagram-7LVT6UL2-DfLTv8zh.js.gz +0 -0
  349. package/dist/client/assets/channel-gcmN0N0m.js +0 -1
  350. package/dist/client/assets/chunk-F27PBJKO-yxCv2Uqk.js.br +0 -0
  351. package/dist/client/assets/chunk-F27PBJKO-yxCv2Uqk.js.gz +0 -0
  352. package/dist/client/assets/chunk-IMKFNOWR-CdPjlb9f.js.br +0 -0
  353. package/dist/client/assets/chunk-IMKFNOWR-CdPjlb9f.js.gz +0 -0
  354. package/dist/client/assets/chunk-SVP7TREG-D1tOJung.js.br +0 -0
  355. package/dist/client/assets/chunk-SVP7TREG-D1tOJung.js.gz +0 -0
  356. package/dist/client/assets/chunk-TICWLB2K-DZjDfYNw.js.br +0 -0
  357. package/dist/client/assets/chunk-TICWLB2K-DZjDfYNw.js.gz +0 -0
  358. package/dist/client/assets/classDiagram-ZZMXUADV-k4BRymfG.js +0 -1
  359. package/dist/client/assets/classDiagram-v2-VYDZK3BY-k4BRymfG.js +0 -1
  360. package/dist/client/assets/cose-bilkent-JH36ORCC-C4Wb5lqD.js.br +0 -0
  361. package/dist/client/assets/cose-bilkent-JH36ORCC-C4Wb5lqD.js.gz +0 -0
  362. package/dist/client/assets/cynefin-OW5HDTMX-BvWKB1Xk.js.br +0 -0
  363. package/dist/client/assets/cynefin-OW5HDTMX-BvWKB1Xk.js.gz +0 -0
  364. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-7tAkHD2P.js.br +0 -0
  365. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-7tAkHD2P.js.gz +0 -0
  366. package/dist/client/assets/dagre-GXQ25YYZ-Bs6IPwks.js.br +0 -0
  367. package/dist/client/assets/dagre-GXQ25YYZ-Bs6IPwks.js.gz +0 -0
  368. package/dist/client/assets/diagram-S7CK7UJ4-CmYXaTya.js.br +0 -0
  369. package/dist/client/assets/diagram-S7CK7UJ4-CmYXaTya.js.gz +0 -0
  370. package/dist/client/assets/diagram-UQ7AKVKN-CqxoefB7.js.br +0 -0
  371. package/dist/client/assets/diagram-UQ7AKVKN-CqxoefB7.js.gz +0 -0
  372. package/dist/client/assets/diagram-VSXAHHWV-Dxp1MKPT.js.br +0 -0
  373. package/dist/client/assets/diagram-VSXAHHWV-Dxp1MKPT.js.gz +0 -0
  374. package/dist/client/assets/diagram-VX7I27RA-BKbWvvEr.js.br +0 -0
  375. package/dist/client/assets/diagram-Z3DM3KII-BZnNaP7F.js.br +0 -0
  376. package/dist/client/assets/diagram-Z3DM3KII-BZnNaP7F.js.gz +0 -0
  377. package/dist/client/assets/ebnfDiagram-PWID7BFC-BXVlN_P7.js.br +0 -0
  378. package/dist/client/assets/ebnfDiagram-PWID7BFC-BXVlN_P7.js.gz +0 -0
  379. package/dist/client/assets/erDiagram-RLTQ6QDP-CEdYDZU7.js.br +0 -0
  380. package/dist/client/assets/erDiagram-RLTQ6QDP-CEdYDZU7.js.gz +0 -0
  381. package/dist/client/assets/flowDiagram-HODETNUW-Bbw8GPCZ.js.br +0 -0
  382. package/dist/client/assets/flowDiagram-HODETNUW-Bbw8GPCZ.js.gz +0 -0
  383. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CcFbtP2y.js.br +0 -0
  384. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CcFbtP2y.js.gz +0 -0
  385. package/dist/client/assets/gestureArbiter-sdzs-66-.js +0 -1
  386. package/dist/client/assets/gestureArbiter-sdzs-66-.js.br +0 -0
  387. package/dist/client/assets/gestureArbiter-sdzs-66-.js.gz +0 -0
  388. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Eiust154.js.br +0 -0
  389. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Eiust154.js.gz +0 -0
  390. package/dist/client/assets/index-DtPsPZ7W.css +0 -1
  391. package/dist/client/assets/index-DtPsPZ7W.css.br +0 -0
  392. package/dist/client/assets/index-DtPsPZ7W.css.gz +0 -0
  393. package/dist/client/assets/ishikawaDiagram-5VMMS53U-qTwgXfx_.js.gz +0 -0
  394. package/dist/client/assets/journeyDiagram-3NMN7TZE-DKy1msWo.js.br +0 -0
  395. package/dist/client/assets/journeyDiagram-3NMN7TZE-DKy1msWo.js.gz +0 -0
  396. package/dist/client/assets/kanban-definition-UXKFOSKX-BxibybDd.js.br +0 -0
  397. package/dist/client/assets/kanban-definition-UXKFOSKX-BxibybDd.js.gz +0 -0
  398. package/dist/client/assets/layout-Bh6fKHRM.js.br +0 -0
  399. package/dist/client/assets/layout-Bh6fKHRM.js.gz +0 -0
  400. package/dist/client/assets/linear-SRlL3VWX.js.gz +0 -0
  401. package/dist/client/assets/mermaid.core-D0bcNdw3.js.br +0 -0
  402. package/dist/client/assets/mermaid.core-D0bcNdw3.js.gz +0 -0
  403. package/dist/client/assets/mindmap-definition-YA3MSWOX-CImZ_aI0.js.br +0 -0
  404. package/dist/client/assets/mindmap-definition-YA3MSWOX-CImZ_aI0.js.gz +0 -0
  405. package/dist/client/assets/passwordBootstrap-f7-mFb1b.js +0 -2
  406. package/dist/client/assets/passwordBootstrap-f7-mFb1b.js.br +0 -0
  407. package/dist/client/assets/passwordBootstrap-f7-mFb1b.js.gz +0 -0
  408. package/dist/client/assets/pegDiagram-XKGWAZYB-BT4pTf8O.js.br +0 -0
  409. package/dist/client/assets/pegDiagram-XKGWAZYB-BT4pTf8O.js.gz +0 -0
  410. package/dist/client/assets/pieDiagram-E7YTZNPT-C5K58ien.js.br +0 -0
  411. package/dist/client/assets/pieDiagram-E7YTZNPT-C5K58ien.js.gz +0 -0
  412. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DzSugYXz.js.br +0 -0
  413. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DzSugYXz.js.gz +0 -0
  414. package/dist/client/assets/railroadDiagram-O6MQD6OU-BY07SouQ.js.br +0 -0
  415. package/dist/client/assets/railroadDiagram-O6MQD6OU-BY07SouQ.js.gz +0 -0
  416. package/dist/client/assets/rdpSession-RNjO6yLw.js +0 -1
  417. package/dist/client/assets/rdpSession-RNjO6yLw.js.br +0 -0
  418. package/dist/client/assets/rdpSession-RNjO6yLw.js.gz +0 -0
  419. package/dist/client/assets/referenceLongPress-B9K9yPKR.js +0 -1
  420. package/dist/client/assets/referenceLongPress-B9K9yPKR.js.br +0 -0
  421. package/dist/client/assets/referenceLongPress-B9K9yPKR.js.gz +0 -0
  422. package/dist/client/assets/requirementDiagram-BXWQKSXE-C7WieVuE.js.br +0 -0
  423. package/dist/client/assets/requirementDiagram-BXWQKSXE-C7WieVuE.js.gz +0 -0
  424. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BJsDWNs4.js.br +0 -0
  425. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BJsDWNs4.js.gz +0 -0
  426. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BeJ_7853.js.gz +0 -0
  427. package/dist/client/assets/sizeCapture-INFHLROL-DOsjXKPV.js.br +0 -0
  428. package/dist/client/assets/sizeCapture-INFHLROL-DOsjXKPV.js.gz +0 -0
  429. package/dist/client/assets/stateDiagram-D77RDMKH-6s709_RT.js.br +0 -0
  430. package/dist/client/assets/stateDiagram-D77RDMKH-6s709_RT.js.gz +0 -0
  431. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-B43A9Hsz.js +0 -1
  432. package/dist/client/assets/swimlanes-42K2YHIH-5R5dw9_R.js.br +0 -0
  433. package/dist/client/assets/swimlanes-42K2YHIH-5R5dw9_R.js.gz +0 -0
  434. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DoFbqN_4.js +0 -8
  435. package/dist/client/assets/timeline-definition-24CTP7MA-B4YN0lsx.js.br +0 -0
  436. package/dist/client/assets/timeline-definition-24CTP7MA-B4YN0lsx.js.gz +0 -0
  437. package/dist/client/assets/triangle-alert-BzBDJvJX.js +0 -1
  438. package/dist/client/assets/vennDiagram-4TSXK5OY-Wd83SmWI.js.br +0 -0
  439. package/dist/client/assets/vennDiagram-4TSXK5OY-Wd83SmWI.js.gz +0 -0
  440. package/dist/client/assets/videoCompression-Bsw--tMn.js +0 -3
  441. package/dist/client/assets/videoCompression-Bsw--tMn.js.br +0 -0
  442. package/dist/client/assets/videoCompression-Bsw--tMn.js.gz +0 -0
  443. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CGsYvqWN.js.br +0 -0
  444. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CGsYvqWN.js.gz +0 -0
  445. package/dist/client/assets/xychartDiagram-S5SC5T6Z-ednJq1_u.js.gz +0 -0
  446. /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css → swiper-C2J0D0kV.css} +0 -0
  447. /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css.br → swiper-C2J0D0kV.css.br} +0 -0
  448. /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css.gz → swiper-C2J0D0kV.css.gz} +0 -0
@@ -1,3 +1,4 @@
1
+ import { validShareResources, resourceSubset } from '../shared/shareResources.js';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { mkdirSync, readFileSync, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync } from 'node:fs';
3
4
  import { dirname } from 'node:path';
@@ -18,6 +19,7 @@ function validateInput(v) {
18
19
  assert(v.scope.kind === 'sessions' && ids(v.scope.sessionIds), 'session scope');
19
20
  keys(v.scope, ['kind', 'sessionIds']);
20
21
  }
22
+ assert(v.resources === undefined || validShareResources(v.resources), 'shared resources');
21
23
  assert(Array.isArray(v.actions) && v.actions.length > 0 && v.actions.length <= 256 && v.actions.every(action) && new Set(v.actions).size === v.actions.length, 'actions');
22
24
  if (v.scope.kind === 'sessions')
23
25
  assert(v.actions.every(a => a.startsWith('session.')), 'session scope cannot grant service or file capabilities');
@@ -37,7 +39,7 @@ function isSubset(child, parent) {
37
39
  return false;
38
40
  if (parent.scope.kind === 'sessions' && (child.scope.kind !== 'sessions' || !child.scope.sessionIds.every(s => parent.scope.kind === 'sessions' && parent.scope.sessionIds.includes(s))))
39
41
  return false;
40
- if (!parent.actions.includes('service:*') && !child.actions.every(a => parent.actions.includes(a)))
42
+ if (!parent.actions.includes('service:*') && (!child.actions.every(a => parent.actions.includes(a)) || !resourceSubset(child.resources, parent.resources)))
41
43
  return false;
42
44
  for (const key of ['entryServiceIds', 'relayIds']) {
43
45
  const limit = parent.conditions?.[key];
@@ -86,7 +88,7 @@ export class AuthorizationStore {
86
88
  for (const value of data.grants) {
87
89
  validateInput(value);
88
90
  const g = value;
89
- keys(g, ['id', 'serviceId', 'createdAt', 'parentGrantId', 'revokedAt', 'subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'passwordCredential']);
91
+ keys(g, ['id', 'serviceId', 'createdAt', 'parentGrantId', 'revokedAt', 'subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'passwordCredential', 'resources']);
90
92
  assert(id(g.id) && g.serviceId === this.options.serviceId && Number.isSafeInteger(g.createdAt) && g.createdAt >= 0, 'grant metadata');
91
93
  assert(g.parentGrantId === undefined || id(g.parentGrantId), 'parent identity');
92
94
  assert(g.revokedAt === undefined || (Number.isSafeInteger(g.revokedAt) && g.revokedAt >= 0), 'revocation');
@@ -122,7 +124,7 @@ export class AuthorizationStore {
122
124
  }
123
125
  create(input) {
124
126
  validateInput(input);
125
- keys(input, ['subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions']);
127
+ keys(input, ['subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'resources']);
126
128
  const now = this.now();
127
129
  assert(input.expiresAt === undefined || input.expiresAt > now, 'already expired');
128
130
  return { ...structuredClone(input), id: randomUUID(), serviceId: this.options.serviceId, createdAt: now };
@@ -169,6 +171,27 @@ export class AuthorizationStore {
169
171
  return true;
170
172
  }
171
173
  list() { return this.read(); }
174
+ listActive() { const grants = this.read(); return grants.filter(grant => active(grant, grants, this.now(), new Set(), this.options.passwordCredential?.())); }
175
+ /** Narrow only this Session, preserving sibling Session grants and descendant checks. */
176
+ revokeSession(sessionId, normalize = id => id) {
177
+ const grants = this.read();
178
+ let changed = 0;
179
+ for (const grant of grants) {
180
+ if (grant.revokedAt !== undefined || grant.scope.kind !== 'sessions')
181
+ continue;
182
+ const remaining = grant.scope.sessionIds.filter(id => normalize(id) !== sessionId);
183
+ if (remaining.length === grant.scope.sessionIds.length)
184
+ continue;
185
+ changed++;
186
+ if (!remaining.length)
187
+ grant.revokedAt = this.now();
188
+ else
189
+ grant.scope = { kind: 'sessions', sessionIds: remaining };
190
+ }
191
+ if (changed)
192
+ this.save(grants);
193
+ return changed;
194
+ }
172
195
  listEffective(request) {
173
196
  if (!request || !id(request.subjectId) || request.serviceId !== this.options.serviceId)
174
197
  return [];
@@ -1,3 +1,4 @@
1
+ import { validShareResources } from '../shared/shareResources.js';
1
2
  import { createHash, randomBytes, randomUUID } from 'node:crypto';
2
3
  import { mkdirSync, readFileSync, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync, chmodSync } from 'node:fs';
3
4
  import { dirname } from 'node:path';
@@ -8,8 +9,9 @@ const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
8
9
  function requireValid(ok) { if (!ok)
9
10
  throw new Error('INVALID_INVITATION'); }
10
11
  export function validateInvitation(input) {
11
- requireValid(record(input) && Object.keys(input).every(k => ['scope', 'actions', 'label', 'expiresAt'].includes(k)));
12
+ requireValid(record(input) && Object.keys(input).every(k => ['scope', 'actions', 'label', 'expiresAt', 'resources'].includes(k)));
12
13
  requireValid(record(input.scope) && Array.isArray(input.actions) && input.actions.length > 0 && new Set(input.actions).size === input.actions.length);
14
+ requireValid(input.resources === undefined || validShareResources(input.resources));
13
15
  const scope = input.scope;
14
16
  if (scope.kind === 'service') {
15
17
  requireValid(Object.keys(scope).length === 1 && ((input.actions.length === 1 && input.actions[0] === 'service:*') || input.actions.every(a => typeof a === 'string' && actions.has(a))));
@@ -50,9 +52,9 @@ export class InvitationStore {
50
52
  requireValid(record(data) && data.version === 1 && data.serviceId === this.options.serviceId && Array.isArray(data.invitations) && data.invitations.length <= 2048);
51
53
  const seen = new Set();
52
54
  for (const item of data.invitations) {
53
- requireValid(record(item) && Object.keys(item).every(k => ['scope', 'actions', 'label', 'expiresAt', 'hash', 'issuerId', 'createdAt', 'invitationExpiresAt', 'consumedAt', 'consumedBy'].includes(k)));
54
- const { scope, actions, label, expiresAt, hash, issuerId, createdAt, invitationExpiresAt, consumedAt, consumedBy } = item;
55
- validateInvitation({ scope, actions, ...(label === undefined ? {} : { label }), ...(expiresAt === undefined ? {} : { expiresAt }) });
55
+ requireValid(record(item) && Object.keys(item).every(k => ['scope', 'actions', 'label', 'expiresAt', 'hash', 'issuerId', 'createdAt', 'invitationExpiresAt', 'consumedAt', 'consumedBy', 'resources'].includes(k)));
56
+ const { scope, actions, resources, label, expiresAt, hash, issuerId, createdAt, invitationExpiresAt, consumedAt, consumedBy } = item;
57
+ validateInvitation({ scope, actions, ...(resources === undefined ? {} : { resources }), ...(label === undefined ? {} : { label }), ...(expiresAt === undefined ? {} : { expiresAt }) });
56
58
  requireValid(typeof hash === 'string' && /^[a-f0-9]{64}$/.test(hash) && !seen.has(hash) && identity(issuerId));
57
59
  seen.add(hash);
58
60
  requireValid(Number.isSafeInteger(createdAt) && Number.isSafeInteger(invitationExpiresAt) && Number(invitationExpiresAt) === Number(createdAt) + TTL);
@@ -87,7 +89,7 @@ export class InvitationStore {
87
89
  listPending() {
88
90
  const now = this.now();
89
91
  return this.read().filter(i => i.consumedAt === undefined && i.invitationExpiresAt > now && (i.expiresAt === undefined || i.expiresAt > now))
90
- .map(({ hash, scope, actions, label, expiresAt, createdAt, invitationExpiresAt }) => ({ id: hash, scope, actions, ...(label ? { label } : {}), ...(expiresAt === undefined ? {} : { expiresAt }), createdAt, invitationExpiresAt }));
92
+ .map(({ hash, scope, actions, resources, label, expiresAt, createdAt, invitationExpiresAt }) => ({ id: hash, scope, actions, ...(resources === undefined ? {} : { resources }), ...(label ? { label } : {}), ...(expiresAt === undefined ? {} : { expiresAt }), createdAt, invitationExpiresAt }));
91
93
  }
92
94
  revoke(id) {
93
95
  const current = this.read();
@@ -98,6 +100,25 @@ export class InvitationStore {
98
100
  this.save(current);
99
101
  return true;
100
102
  }
103
+ revokeSession(sessionId, normalize = id => id) {
104
+ const current = this.read();
105
+ let changed = 0;
106
+ const next = current.filter(invitation => {
107
+ if (invitation.consumedAt !== undefined || invitation.scope.kind !== 'sessions')
108
+ return true;
109
+ const remaining = invitation.scope.sessionIds.filter(id => normalize(id) !== sessionId);
110
+ if (remaining.length === invitation.scope.sessionIds.length)
111
+ return true;
112
+ changed++;
113
+ if (!remaining.length)
114
+ return false;
115
+ invitation.scope = { kind: 'sessions', sessionIds: remaining };
116
+ return true;
117
+ });
118
+ if (changed)
119
+ this.save(next);
120
+ return changed;
121
+ }
101
122
  create(issuerId, input) {
102
123
  requireValid(identity(issuerId));
103
124
  validateInvitation(input);
@@ -129,7 +150,7 @@ export class InvitationStore {
129
150
  invite.consumedAt = now;
130
151
  invite.consumedBy = subjectId;
131
152
  this.save(current);
132
- return { subjectId, scope: structuredClone(invite.scope), actions: [...invite.actions], ...(invite.expiresAt === undefined ? {} : { expiresAt: invite.expiresAt }), canDelegate: false };
153
+ return { subjectId, ...(invite.resources ? { resources: structuredClone(invite.resources) } : {}), scope: structuredClone(invite.scope), actions: [...invite.actions], ...(invite.expiresAt === undefined ? {} : { expiresAt: invite.expiresAt }), canDelegate: false };
133
154
  }
134
155
  subjectLabels() {
135
156
  return Object.fromEntries(this.read().filter(i => i.consumedBy && i.label).map(i => [i.consumedBy, i.label]));
@@ -211,7 +211,8 @@ export class RelayRouter {
211
211
  s.provider.streams.delete(s.providerId);
212
212
  send(s.provider.ws, { type: 'close', streamId: s.providerId, reason }, this.limits);
213
213
  }
214
- send(s.owner.ws, { type: 'close', streamId: s.ownerId, reason }, this.limits);
214
+ send(s.owner.ws, { type: 'close', streamId: s.ownerId, reason,
215
+ ...(!this.canRoute(s.owner, s.serviceId) ? { code: 'RELAY_AUTHORIZATION_REQUIRED' } : {}) }, this.limits);
215
216
  }
216
217
  providerFailed(s, reason) {
217
218
  if (s.owner.streams.get(s.ownerId) !== s)
@@ -240,7 +241,19 @@ export class RelayRouter {
240
241
  provider.streams.delete(s.providerId);
241
242
  continue;
242
243
  }
243
- s.deadline = setTimeout(() => this.providerFailed(s, 'Target timeout'), Math.min(this.limits.connectTimeoutMs, s.expires - Date.now()));
244
+ const deadline = Math.min(Date.now() + this.limits.connectTimeoutMs, s.expires);
245
+ const expire = () => {
246
+ // Timers can fire just before their absolute deadline. Do not fail a
247
+ // provider early and start a backup beyond the original route budget.
248
+ const remaining = deadline - Date.now();
249
+ if (remaining > 0) {
250
+ s.deadline = setTimeout(expire, remaining);
251
+ s.deadline.unref?.();
252
+ return;
253
+ }
254
+ this.providerFailed(s, 'Target timeout');
255
+ };
256
+ s.deadline = setTimeout(expire, Math.max(0, deadline - Date.now()));
244
257
  s.deadline.unref?.();
245
258
  return true;
246
259
  }
@@ -264,10 +277,15 @@ export class RelayRouter {
264
277
  return;
265
278
  }
266
279
  if (f.type === 'open') {
280
+ if (this.options.isLocalTarget?.(f.serviceId)) {
281
+ send(peer.ws, { type: 'close', streamId: f.streamId, reason: 'Cannot relay to the entry itself', code: 'RELAY_SELF_TARGET' }, this.limits);
282
+ return;
283
+ }
267
284
  if (peer.streams.has(f.streamId))
268
285
  throw new Error('Duplicate stream');
269
286
  if (!this.canRoute(peer, f.serviceId) || !this.hasRoute(f.serviceId)) {
270
- send(peer.ws, { type: 'close', streamId: f.streamId, reason: 'Route unavailable or denied' }, this.limits);
287
+ send(peer.ws, { type: 'close', streamId: f.streamId, reason: 'Route unavailable or denied',
288
+ ...(!this.canRoute(peer, f.serviceId) ? { code: 'RELAY_AUTHORIZATION_REQUIRED' } : {}) }, this.limits);
271
289
  return;
272
290
  }
273
291
  if (peer.streams.size >= this.limits.maxStreams)
@@ -1,9 +1,13 @@
1
1
  // Object identity is deliberately used instead of a remotely forgeable header.
2
+ const resources = new WeakMap();
3
+ export function encryptedRequestResources(request) { return resources.get(request); }
2
4
  const verified = new WeakSet();
3
5
  const subjects = new WeakMap();
4
6
  const serviceAccess = new WeakSet();
5
- export function markEncryptedRequest(request, subjectId, fullService = false) {
7
+ export function markEncryptedRequest(request, subjectId, fullService = false, shared) {
6
8
  verified.add(request);
9
+ if (shared)
10
+ resources.set(request, shared);
7
11
  if (subjectId)
8
12
  subjects.set(request, subjectId);
9
13
  if (fullService)
@@ -0,0 +1,63 @@
1
+ import { realpathSync, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { canonicalApiPath } from './accessPolicy.js';
4
+ export function computerResource(resources, host, protocol = 'vnc', port = protocol === 'rdp' ? 3389 : 5900) {
5
+ const matches = resources.computers?.filter(c => c.host.toLowerCase() === host.toLowerCase() && c.protocol === protocol && c.port === port);
6
+ return matches?.find(c => c.access === 'control') ?? matches?.[0];
7
+ }
8
+ /** Check both the lexical and real path. Symlinks cannot widen a shared root. */
9
+ export function directoryResource(resources, requested, write = false) {
10
+ if (!path.isAbsolute(requested) || requested.includes('\0'))
11
+ return false;
12
+ try {
13
+ const lexical = path.resolve(requested), real = realpathSync(lexical);
14
+ const inside = (root, file) => { const rel = path.relative(root, file); return rel === '' || (!rel.startsWith(`..${path.sep}`) && rel !== '..' && !path.isAbsolute(rel)); };
15
+ return resources.directories?.some(root => (!write || root.access === 'write') && inside(path.resolve(root.path), lexical) && inside(realpathSync(root.path), real)) === true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ export function canonicalShareDirectories(resources) {
22
+ return { ...resources, ...(resources.directories ? { directories: resources.directories.map(root => {
23
+ const canonical = realpathSync(root.path);
24
+ if (!statSync(canonical).isDirectory())
25
+ throw new Error('SHARE_DIRECTORY_UNAVAILABLE');
26
+ return { ...root, path: canonical };
27
+ }) } : {}) };
28
+ }
29
+ export function resourceHttpAllowed(resources, method, requestPath) {
30
+ const pathname = canonicalApiPath(requestPath);
31
+ if (!pathname)
32
+ return false;
33
+ if (method === 'GET' && pathname === '/api/android/devices')
34
+ return !!resources.android?.length;
35
+ if (resources.android?.length && ((['GET', 'POST'].includes(method) && pathname === '/api/android/recordings') || (method === 'POST' && /^\/api\/android\/recordings\/[a-f0-9-]{36}\/(?:stop|save)$/.test(pathname)) || (method === 'DELETE' && /^\/api\/android\/recordings\/[a-f0-9-]{36}$/.test(pathname))))
36
+ return true;
37
+ if (method === 'GET' && pathname === '/api/computer/status')
38
+ return !!resources.computers?.length;
39
+ const url = new URL(requestPath, 'http://inner');
40
+ const op = pathname.slice('/api/terminal/fs/'.length);
41
+ if (['GET', 'HEAD'].includes(method) && op.startsWith('preview/')) {
42
+ const parts = op.slice(8).split('/');
43
+ if (/^[0-9a-f]{32}$/.test(parts[0]))
44
+ parts.shift();
45
+ return directoryResource(resources, '/' + parts.join('/'));
46
+ }
47
+ if (!pathname.startsWith('/api/terminal/fs/'))
48
+ return false;
49
+ // Unregistered Git, shell, native-open and recursive APIs remain unavailable.
50
+ const read = ['GET', 'HEAD'].includes(method) && ['list', 'read', 'blob', 'video', 'download', 'eda-preview', 'eda-inspect'].includes(op);
51
+ const write = (method === 'POST' && op === 'upload') || (method === 'PUT' && op === 'write') || (method === 'DELETE' && op === 'file');
52
+ if (!read && !write)
53
+ return false;
54
+ const key = op === 'upload' ? 'dir' : 'path';
55
+ if (url.searchParams.getAll(key).length !== 1 || !directoryResource(resources, url.searchParams.get(key) ?? '', write))
56
+ return false;
57
+ // Listing can run git check-ignore; its repository is another filesystem input.
58
+ for (const extra of ['gitIgnoreRoot', 'cwd', 'root', 'repoRoot']) {
59
+ if (url.searchParams.has(extra) && (url.searchParams.getAll(extra).length !== 1 || !directoryResource(resources, url.searchParams.get(extra) ?? '')))
60
+ return false;
61
+ }
62
+ return true;
63
+ }
@@ -1,3 +1,7 @@
1
+ import { SessionSharingPresence } from './sessionSharing.js';
2
+ import { collaborationSessionDisplayName } from '../utils/sessionDisplayName.js';
3
+ import { mergeShareResources } from '../shared/shareResources.js';
4
+ import { canonicalShareDirectories, computerResource, resourceHttpAllowed } from './resourceAccess.js';
1
5
  import { CollaborationRpc } from '../agent/collaborationPeerTransport.js';
2
6
  import { PasswordBootstrapServer } from './passwordBootstrap.js';
3
7
  import { DeviceProfiles } from './deviceProfiles.js';
@@ -92,6 +96,7 @@ export async function createFederationRuntime(app, directory, handlers, options
92
96
  const passwordStarts = [];
93
97
  const passwordAttempts = new Map();
94
98
  const store = new AuthorizationStore({ serviceId, filePath: join(directory, 'grants.json'), passwordCredential: getPasswordCredentialFingerprint });
99
+ const sharingPresence = new SessionSharingPresence();
95
100
  const deviceProfiles = new DeviceProfiles(join(directory, 'device-profiles.json'));
96
101
  const deviceNames = new DeviceNames(join(directory, 'device-names.json'));
97
102
  const invitations = new InvitationStore({ serviceId, filePath: join(directory, 'invitations.json') });
@@ -113,7 +118,9 @@ export async function createFederationRuntime(app, directory, handlers, options
113
118
  delete request.headers['x-termdock-device'];
114
119
  const fullService = request.headers['x-termdock-full-service'] === '1';
115
120
  delete request.headers['x-termdock-full-service'];
116
- markEncryptedRequest(request, typeof subject === 'string' ? subject : undefined, fullService);
121
+ const resourcesHeader = request.headers['x-termdock-resources'];
122
+ delete request.headers['x-termdock-resources'];
123
+ markEncryptedRequest(request, typeof subject === 'string' ? subject : undefined, fullService, typeof resourcesHeader === 'string' ? JSON.parse(resourcesHeader) : undefined);
117
124
  app(request, response);
118
125
  });
119
126
  // The outer encrypted consumer can apply backpressure long after a local
@@ -163,10 +170,14 @@ export async function createFederationRuntime(app, directory, handlers, options
163
170
  const stable = stableSessionId(sessionId);
164
171
  return stable !== undefined && store.authorize({ subjectId, serviceId, action, sessionId: stable }).allowed;
165
172
  };
173
+ const resources = (subjectId) => mergeShareResources(store.listEffective({ subjectId, serviceId }).map(g => g.resources));
174
+ const androidAllowed = (subjectId, serial, control = false) => full(subjectId) || resources(subjectId).android?.some(d => d.serial === serial && (!control || d.access === 'control')) === true;
175
+ const computerAllowed = (subjectId, target) => full(subjectId) || !!computerResource(resources(subjectId), target.host, target.protocol, target.port);
166
176
  const full = (subjectId) => open() || store.hasFullServiceAccess({ subjectId, serviceId });
167
177
  let channel;
168
178
  const operations = new Map();
169
179
  const sockets = new Map();
180
+ const presenceKey = randomUUID();
170
181
  let revokeTimer;
171
182
  try {
172
183
  const secured = await secureConnection({ identity, duplex, initiator: false, signal: AbortSignal.timeout(15000) });
@@ -180,23 +191,26 @@ export async function createFederationRuntime(app, directory, handlers, options
180
191
  if (typeof head.method !== 'string' || typeof head.path !== 'string' || !isBusinessApiPath(head.method, head.path))
181
192
  throw new Error('API_NOT_ALLOWED');
182
193
  const policy = resolveAccessRequest(head.method, head.path);
183
- if (!full(subjectId)) {
194
+ if (!full(subjectId) && !resourceHttpAllowed(resources(subjectId), head.method, head.path)) {
184
195
  if (!policy)
185
196
  throw new Error('API_NOT_ALLOWED');
186
197
  check(policy.action, policy.sessionId);
187
198
  }
188
199
  };
189
200
  async function runHttp(id, operation) {
201
+ const scopeStamp = JSON.stringify([full(subjectId), resources(subjectId)]);
190
202
  const sendHttp = (packet) => {
191
203
  if (operations.get(id) !== operation || operation.abort.signal.aborted)
192
204
  throw new Error('CANCELLED');
205
+ if (scopeStamp !== JSON.stringify([full(subjectId), resources(subjectId)]))
206
+ throw new Error('AUTHORIZATION_CHANGED');
193
207
  httpAllowed(operation.head);
194
208
  send(packet);
195
209
  };
196
210
  try {
197
211
  httpAllowed(operation.head);
198
212
  const headers = new Headers({ 'x-termdock-inner': internalToken, 'x-termdock-device': subjectId,
199
- 'x-termdock-full-service': full(subjectId) ? '1' : '0' });
213
+ 'x-termdock-full-service': full(subjectId) ? '1' : '0', 'x-termdock-resources': JSON.stringify(resources(subjectId)) });
200
214
  const supplied = operation.head.headers;
201
215
  if (supplied && typeof supplied === 'object')
202
216
  for (const [name, value] of Object.entries(supplied)) {
@@ -259,9 +273,9 @@ export async function createFederationRuntime(app, directory, handlers, options
259
273
  }
260
274
  }
261
275
  revokeTimer = setInterval(() => {
262
- for (const { socket: logical, sessionId, android, computer } of sockets.values()) {
276
+ for (const { socket: logical, sessionId, android, computer, computerTarget, computerControl } of sockets.values()) {
263
277
  if (android || computer) {
264
- if (!full(subjectId))
278
+ if (android ? !androidAllowed(subjectId, android) : !computerTarget || !computerAllowed(subjectId, computerTarget) || (computerControl && !full(subjectId) && !resources(subjectId).computers?.some(c => c.host.toLowerCase() === computerTarget.host.toLowerCase() && c.protocol === computerTarget.protocol && c.port === computerTarget.port && c.access === 'control')))
265
279
  logical.close(4003, 'Authorization revoked');
266
280
  continue;
267
281
  }
@@ -389,7 +403,7 @@ export async function createFederationRuntime(app, directory, handlers, options
389
403
  }
390
404
  else if (packet.type === 'permissions') {
391
405
  const grants = store.listEffective({ subjectId, serviceId });
392
- send({ type: 'result', id: packet.id, subjectId, serviceId, fullService: full(subjectId), grants, httpWindow: MAX_TRANSFER_WINDOW, binaryChunks: true, webTransport: options.webTransport?.() });
406
+ send({ type: 'result', id: packet.id, subjectId, serviceId, fullService: full(subjectId), grants, httpWindow: MAX_TRANSFER_WINDOW, binaryChunks: true, relayAuthorizationErrors: true, relaySelfTargetGuard: true, webTransport: options.webTransport?.() });
393
407
  }
394
408
  else if (packet.type === 'session-list') {
395
409
  const inventory = await sessionInventory();
@@ -400,17 +414,34 @@ export async function createFederationRuntime(app, directory, handlers, options
400
414
  return [];
401
415
  seen.add(sessionId);
402
416
  return [{ sessionId, ...(typeof session.sessionId === 'string' ? { sourceSessionId: session.sessionId } : {}),
403
- name: typeof session.name === 'string' ? session.name : sessionId, live: session.live === true,
417
+ name: collaborationSessionDisplayName({ ...session, name: typeof session.name === 'string' ? session.name : sessionId }), live: session.live === true,
404
418
  canWrite: allowed(subjectId, 'session.input', sessionId), canResize: allowed(subjectId, 'session.resize', sessionId) }];
405
419
  });
406
420
  send({ type: 'result', id: packet.id, items });
407
421
  }
422
+ else if (packet.type === 'session-sharing-status') {
423
+ check('authorization.manage');
424
+ const normalize = (id) => options.resolveSessionId?.(id) || id;
425
+ send({ type: 'result', id: packet.id, items: sharingPresence.summary(store.listActive(), invitations.listPending(), normalize) });
426
+ }
427
+ else if (packet.type === 'session-sharing-stop') {
428
+ check('authorization.manage');
429
+ if (typeof packet.sessionId !== 'string' || !packet.sessionId)
430
+ throw new Error('INVALID_SESSION');
431
+ const normalize = (id) => options.resolveSessionId?.(id) || id;
432
+ const sessionId = normalize(packet.sessionId);
433
+ const revoked = store.revokeSession(sessionId, normalize);
434
+ invitations.revokeSession(sessionId, normalize);
435
+ send({ type: 'result', id: packet.id, revoked });
436
+ }
408
437
  else if (packet.type === 'self-relay-ticket') {
409
438
  if (!options.issueSelfRelayTicket)
410
439
  throw new Error('SELF_RELAY_UNAVAILABLE');
411
440
  send({ type: 'result', id: packet.id, serviceId: subjectId, ...options.issueSelfRelayTicket(subjectId) });
412
441
  }
413
442
  else if (packet.type === 'route-invite-create') {
443
+ if (packet.serviceId === serviceId)
444
+ throw new Error('RELAY_SELF_TARGET');
414
445
  check('authorization.manage');
415
446
  if (!options.createRouteInvitation || typeof packet.serviceId !== 'string' || !packet.serviceId || packet.serviceId.length > 512)
416
447
  throw new Error('ROUTE_NOT_AVAILABLE');
@@ -433,6 +464,8 @@ export async function createFederationRuntime(app, directory, handlers, options
433
464
  send({ type: 'result', id: packet.id, canManage, grants: canManage ? all : all.filter(grant => grant.subjectId === subjectId) });
434
465
  }
435
466
  else if (packet.type === 'route-grant') {
467
+ if (packet.serviceId === serviceId)
468
+ throw new Error('RELAY_SELF_TARGET');
436
469
  check('authorization.manage');
437
470
  if (!options.grantRouteAccess || typeof packet.serviceId !== 'string' || typeof packet.subjectId !== 'string')
438
471
  throw new Error('ROUTE_NOT_AVAILABLE');
@@ -447,6 +480,8 @@ export async function createFederationRuntime(app, directory, handlers, options
447
480
  send({ type: 'result', id: packet.id, revoked: options.revokeRouteAccess(packet.grantId) });
448
481
  }
449
482
  else if (packet.type === 'route-ticket') {
483
+ if (packet.serviceId === serviceId)
484
+ throw new Error('RELAY_SELF_TARGET');
450
485
  if (typeof packet.serviceId !== 'string' || !options.hasRouteGrant?.(subjectId, packet.serviceId))
451
486
  throw new Error('AUTHORIZATION_DENIED');
452
487
  if (!options.issueRouteTicket || typeof packet.serviceId !== 'string' || !packet.serviceId || packet.serviceId.length > 512)
@@ -455,10 +490,12 @@ export async function createFederationRuntime(app, directory, handlers, options
455
490
  }
456
491
  else if (packet.type === 'invite-create') {
457
492
  check('authorization.manage');
458
- const invitation = { scope: packet.scope, actions: packet.actions,
493
+ const invitation = { scope: packet.scope, actions: packet.actions, ...(packet.resources === undefined ? {} : { resources: packet.resources }),
459
494
  ...(packet.label === undefined ? {} : { label: packet.label }),
460
495
  ...(packet.expiresAt === undefined ? {} : { expiresAt: packet.expiresAt }) };
461
496
  validateInvitation(invitation);
497
+ if (invitation.resources)
498
+ invitation.resources = canonicalShareDirectories(invitation.resources);
462
499
  if (invitation.scope.kind === 'sessions') {
463
500
  // Bind to the stable client session id, never the transient backend
464
501
  // PTY id: backends are replaced on restart, tmux recovery or
@@ -518,6 +555,9 @@ export async function createFederationRuntime(app, directory, handlers, options
518
555
  if ((context.singleOperation ? operations.size + sockets.size >= 1 : operations.size >= MAX_SERVER_HTTP_REQUESTS) || operations.has(packet.id) || sockets.has(packet.id))
519
556
  throw new Error('STREAM_LIMIT');
520
557
  httpAllowed(packet);
558
+ const requestedSession = resolveAccessRequest(String(packet.method), String(packet.path))?.sessionId;
559
+ if (requestedSession && !full(subjectId))
560
+ sharingPresence.visit(presenceKey, subjectId, stableSessionId(requestedSession) || requestedSession);
521
561
  const upload = !['GET', 'HEAD'].includes(String(packet.method));
522
562
  if (upload && uploadSlots >= 8)
523
563
  throw new Error('UPLOAD_CONCURRENCY_LIMIT');
@@ -581,26 +621,34 @@ export async function createFederationRuntime(app, directory, handlers, options
581
621
  const match = /^\/api\/terminal\/([^/%]+)\/ws$/.exec(url.pathname);
582
622
  const androidMatch = /^\/api\/android\/([^/%]+)\/ws$/.exec(url.pathname);
583
623
  const computer = url.pathname === '/api/computer/ws';
584
- if (match)
624
+ const computerTarget = { host: url.searchParams.get('host') ?? '', protocol: url.searchParams.get('protocol') ?? 'vnc', port: Number(url.searchParams.get('port') ?? (url.searchParams.get('protocol') === 'rdp' ? '3389' : '5900')) };
625
+ const canView = () => androidMatch ? androidAllowed(subjectId, androidMatch[1]) : computer ? computerAllowed(subjectId, computerTarget) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]);
626
+ if (match) {
585
627
  check('session.view', match[1]);
628
+ if (!full(subjectId))
629
+ sharingPresence.visit(presenceKey, subjectId, stableSessionId(match[1]) || match[1]);
630
+ }
586
631
  else if (url.pathname === '/api/control/ws')
587
632
  check('service.view');
588
633
  else if (androidMatch || computer) {
589
- if (!full(subjectId))
634
+ if (!canView())
590
635
  throw new Error('API_NOT_ALLOWED');
591
636
  }
592
637
  else
593
638
  throw new Error('API_NOT_ALLOWED');
594
- const logical = new LogicalSocket(channel, packet.id, () => (androidMatch || computer) ? full(subjectId) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]), match ? 1024 * 1024 : undefined, !!match && packet.compression === 'gzip');
595
- sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: Boolean(androidMatch), computer,
596
- computerProtocol: computer ? url.searchParams.get('protocol') ?? 'vnc' : undefined });
597
- logical.once('close', () => sockets.delete(packet.id));
639
+ const logical = new LogicalSocket(channel, packet.id, canView, match ? 1024 * 1024 : undefined, !!match && packet.compression === 'gzip');
640
+ sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: androidMatch?.[1], computer, computerTarget: computer ? computerTarget : undefined,
641
+ computerControl: computer && (full(subjectId) || resources(subjectId).computers?.some(c => c.host.toLowerCase() === computerTarget.host.toLowerCase() && c.protocol === computerTarget.protocol && c.port === computerTarget.port && c.access === 'control')), computerProtocol: computer ? url.searchParams.get('protocol') ?? 'vnc' : undefined });
642
+ const shareStreamKey = `${presenceKey}:${packet.id}`;
643
+ if (match && !full(subjectId))
644
+ sharingPresence.openStream(shareStreamKey, presenceKey, subjectId, stableSessionId(match[1]) || match[1]);
645
+ logical.once('close', () => { sockets.delete(packet.id); sharingPresence.closeStream(shareStreamKey); });
598
646
  send({ type: 'ws-ready', id: packet.id });
599
647
  if (match)
600
648
  handlers.terminal(logical, match[1], randomUUID(), {
601
649
  pushClientId: `device:${subjectId}`, sinceSeq: Math.max(0, Number(url.searchParams.get('since')) || 0), streamEpoch: url.searchParams.get('epoch') ?? undefined,
602
650
  flowControl: url.searchParams.get('flow') === '2', outputActive: url.searchParams.get('active') !== '0',
603
- independentTmux: full(subjectId) && url.searchParams.get('transport') === 'tmux-client',
651
+ independentTmux: allowed(subjectId, 'session.resize', match[1]) && url.searchParams.get('transport') === 'tmux-client',
604
652
  }, allowed(subjectId, 'session.resize', match[1]) ? readTerminalHandshakeDimensions(url.searchParams) : undefined);
605
653
  else if (androidMatch) {
606
654
  if (!handlers.android)
@@ -617,7 +665,7 @@ export async function createFederationRuntime(app, directory, handlers, options
617
665
  continue;
618
666
  }
619
667
  handlers.computer(logical, url.searchParams.get('host') ?? '', {
620
- protocol: url.searchParams.get('protocol') ?? undefined, port: url.searchParams.get('port') ?? undefined,
668
+ protocol: computerTarget.protocol, port: String(computerTarget.port), readOnly: !full(subjectId) && computerResource(resources(subjectId), computerTarget.host, computerTarget.protocol, computerTarget.port)?.access !== 'control',
621
669
  });
622
670
  }
623
671
  else
@@ -629,14 +677,14 @@ export async function createFederationRuntime(app, directory, handlers, options
629
677
  throw new Error('INVALID_STREAM');
630
678
  const data = JSON.parse(packet.data);
631
679
  if (item.computer) {
632
- if (!full(subjectId))
680
+ if (!item.computerTarget || !computerAllowed(subjectId, item.computerTarget) || (item.computerControl && !full(subjectId) && !resources(subjectId).computers?.some(c => c.host.toLowerCase() === item.computerTarget.host.toLowerCase() && c.protocol === item.computerTarget.protocol && c.port === item.computerTarget.port && c.access === 'control')))
633
681
  throw new Error('AUTHORIZATION_DENIED');
634
682
  const types = item.computerProtocol === 'rdp' ? ['start', 'instruction', 'ack'] : ['data', 'ack'];
635
683
  if (!types.includes(data?.type))
636
684
  throw new Error('ACTION_NOT_ALLOWED');
637
685
  }
638
686
  else if (item.android) {
639
- if (!full(subjectId))
687
+ if (!androidAllowed(subjectId, item.android, data?.type === 'control' && data?.data !== 'EQ=='))
640
688
  throw new Error('AUTHORIZATION_DENIED');
641
689
  if (!['control', 'ack', 'ping', 'bitrate'].includes(data?.type))
642
690
  throw new Error('ACTION_NOT_ALLOWED');
@@ -672,6 +720,7 @@ export async function createFederationRuntime(app, directory, handlers, options
672
720
  duplex.close?.(new Error('Secure channel closed'));
673
721
  }
674
722
  finally {
723
+ sharingPresence.close(presenceKey);
675
724
  unregisterCollaboration?.();
676
725
  collaborationRpc?.close();
677
726
  clearInterval(revokeTimer);
@@ -0,0 +1,39 @@
1
+ export class SessionSharingPresence {
2
+ connections = new Map();
3
+ visit(connection, device, session) {
4
+ const current = this.connections.get(connection) || { device, sessions: new Map() };
5
+ current.sessions.set(session, Date.now());
6
+ this.connections.set(connection, current);
7
+ }
8
+ streams = new Map();
9
+ openStream(key, connection, device, session) { this.streams.set(key, { connection, device, session }); }
10
+ closeStream(key) { this.streams.delete(key); }
11
+ close(connection) { this.connections.delete(connection); for (const [key, stream] of this.streams)
12
+ if (stream.connection === connection)
13
+ this.streams.delete(key); }
14
+ summary(grants, invitations, normalize) {
15
+ const result = new Map();
16
+ const record = (id) => { const key = normalize(id); let item = result.get(key); if (!item) {
17
+ item = { sessionId: key, devices: new Set(), invitations: 0 };
18
+ result.set(key, item);
19
+ } return item; };
20
+ for (const grant of grants)
21
+ if (grant.scope.kind === 'sessions')
22
+ for (const id of grant.scope.sessionIds)
23
+ record(id).devices.add(grant.subjectId);
24
+ for (const invite of invitations)
25
+ if (invite.scope.kind === 'sessions')
26
+ for (const id of invite.scope.sessionIds)
27
+ record(id).invitations++;
28
+ return [...result.values()].map(item => {
29
+ const online = new Set();
30
+ for (const connection of this.connections.values())
31
+ if (Date.now() - (connection.sessions.get(item.sessionId) || 0) < 15_000 && item.devices.has(connection.device))
32
+ online.add(connection.device);
33
+ for (const stream of this.streams.values())
34
+ if (stream.session === item.sessionId && item.devices.has(stream.device))
35
+ online.add(stream.device);
36
+ return { sessionId: item.sessionId, devices: item.devices.size, connectedDevices: online.size, invitations: item.invitations };
37
+ });
38
+ }
39
+ }
@@ -13,8 +13,8 @@ export function socketDuplex(socket) {
13
13
  close(new Error('Encrypted transport rejected frame'));
14
14
  }
15
15
  });
16
- socket.addEventListener('close', () => input.end());
17
- socket.addEventListener('error', () => input.end(new Error('Encrypted transport disconnected')));
16
+ socket.addEventListener('close', () => input.end(socket.transportFailure));
17
+ socket.addEventListener('error', () => input.end(socket.transportFailure || new Error('Encrypted transport disconnected')));
18
18
  return {
19
19
  source: input, close,
20
20
  async sink(source) {
@@ -28,7 +28,7 @@ export function socketDuplex(socket) {
28
28
  await new Promise(resolve => setTimeout(resolve, 10));
29
29
  }
30
30
  if (socket.readyState !== 1)
31
- throw new Error('Encrypted transport closed');
31
+ throw socket.transportFailure || new Error('Encrypted transport closed');
32
32
  socket.send(chunk.subarray(offset, offset + 64 * 1024));
33
33
  }
34
34
  }