termdock 1.4.248 → 1.4.249

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 (322) hide show
  1. package/dist/client/assets/{App-DA7cQLR7.js → App-ySHLLxfl.js} +35 -35
  2. package/dist/client/assets/App-ySHLLxfl.js.br +0 -0
  3. package/dist/client/assets/App-ySHLLxfl.js.gz +0 -0
  4. package/dist/client/assets/{ChangeWalkthroughPanel-vDiLbIdv.js → ChangeWalkthroughPanel-DrsLVbFw.js} +2 -2
  5. package/dist/client/assets/ChangeWalkthroughPanel-DrsLVbFw.js.br +0 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-DrsLVbFw.js.gz +0 -0
  7. package/dist/client/assets/{DagPlayground-BvY_KblL.js → DagPlayground-CBUX-s8X.js} +1 -1
  8. package/dist/client/assets/DagPlayground-CBUX-s8X.js.br +0 -0
  9. package/dist/client/assets/DagPlayground-CBUX-s8X.js.gz +0 -0
  10. package/dist/client/assets/{DiffLab-Dra7cEKx.js → DiffLab-DD8m9b0G.js} +1 -1
  11. package/dist/client/assets/DiffLab-DD8m9b0G.js.br +0 -0
  12. package/dist/client/assets/DiffLab-DD8m9b0G.js.gz +0 -0
  13. package/dist/client/assets/{DiffReview-v9_1IS2n.js → DiffReview-BEFjWY01.js} +1 -1
  14. package/dist/client/assets/DiffReview-BEFjWY01.js.br +0 -0
  15. package/dist/client/assets/DiffReview-BEFjWY01.js.gz +0 -0
  16. package/dist/client/assets/{DiffReviewLab-BF8Dada6.js → DiffReviewLab-BS1rFkx9.js} +1 -1
  17. package/dist/client/assets/DiffReviewLab-BS1rFkx9.js.br +0 -0
  18. package/dist/client/assets/DiffReviewLab-BS1rFkx9.js.gz +0 -0
  19. package/dist/client/assets/{DiffViewer-C2s4BXp0.js → DiffViewer-zb8C5YLR.js} +1 -1
  20. package/dist/client/assets/DiffViewer-zb8C5YLR.js.br +0 -0
  21. package/dist/client/assets/DiffViewer-zb8C5YLR.js.gz +0 -0
  22. package/dist/client/assets/{ModelPreview-D9pzW_YD.js → ModelPreview-BdY9A7GQ.js} +1 -1
  23. package/dist/client/assets/ModelPreview-BdY9A7GQ.js.br +0 -0
  24. package/dist/client/assets/ModelPreview-BdY9A7GQ.js.gz +0 -0
  25. package/dist/client/assets/RightSidebar-lV2Ca2-c.js +1678 -0
  26. package/dist/client/assets/RightSidebar-lV2Ca2-c.js.br +0 -0
  27. package/dist/client/assets/RightSidebar-lV2Ca2-c.js.gz +0 -0
  28. package/dist/client/assets/{abnfDiagram-VCTEODGH-DVOxqZDY.js → abnfDiagram-VCTEODGH-ChpMiLwB.js} +1 -1
  29. package/dist/client/assets/abnfDiagram-VCTEODGH-ChpMiLwB.js.br +0 -0
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-ChpMiLwB.js.gz +0 -0
  31. package/dist/client/assets/{app-JK4pM9zv.js → app-DhyrRqRS.js} +11 -11
  32. package/dist/client/assets/app-DhyrRqRS.js.br +0 -0
  33. package/dist/client/assets/app-DhyrRqRS.js.gz +0 -0
  34. package/dist/client/assets/{arc-DY_WK1Ln.js → arc-CcPLW1-W.js} +1 -1
  35. package/dist/client/assets/arc-CcPLW1-W.js.br +0 -0
  36. package/dist/client/assets/arc-CcPLW1-W.js.gz +0 -0
  37. package/dist/client/assets/{architectureDiagram-5GKGNRK7-oyhVtQ5E.js → architectureDiagram-5GKGNRK7-CjWa2b5g.js} +1 -1
  38. package/dist/client/assets/architectureDiagram-5GKGNRK7-CjWa2b5g.js.br +0 -0
  39. package/dist/client/assets/{architectureDiagram-5GKGNRK7-oyhVtQ5E.js.gz → architectureDiagram-5GKGNRK7-CjWa2b5g.js.gz} +0 -0
  40. package/dist/client/assets/{blockDiagram-I7D4REHJ-DTMir8MU.js → blockDiagram-I7D4REHJ-D1AMj3Ey.js} +1 -1
  41. package/dist/client/assets/blockDiagram-I7D4REHJ-D1AMj3Ey.js.br +0 -0
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-D1AMj3Ey.js.gz +0 -0
  43. package/dist/client/assets/{c4Diagram-7LVT6UL2-DpDHYMUW.js → c4Diagram-7LVT6UL2-Bo4f-Dyo.js} +1 -1
  44. package/dist/client/assets/c4Diagram-7LVT6UL2-Bo4f-Dyo.js.br +0 -0
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-Bo4f-Dyo.js.gz +0 -0
  46. package/dist/client/assets/channel-B5npd3sc.js +1 -0
  47. package/dist/client/assets/{chunk-2Q5K7J3B-CZw9qV1g.js → chunk-2Q5K7J3B-EA_Ksody.js} +1 -1
  48. package/dist/client/assets/{chunk-5VM5RSS4-7oLZ2vUy.js → chunk-5VM5RSS4-D49nnzNg.js} +1 -1
  49. package/dist/client/assets/{chunk-F27PBJKO-8wDqj2MV.js → chunk-F27PBJKO-BiHLxDgP.js} +1 -1
  50. package/dist/client/assets/chunk-F27PBJKO-BiHLxDgP.js.br +0 -0
  51. package/dist/client/assets/chunk-F27PBJKO-BiHLxDgP.js.gz +0 -0
  52. package/dist/client/assets/{chunk-IMKFNOWR-BnRNosQc.js → chunk-IMKFNOWR-B_x-RlD-.js} +1 -1
  53. package/dist/client/assets/chunk-IMKFNOWR-B_x-RlD-.js.br +0 -0
  54. package/dist/client/assets/chunk-IMKFNOWR-B_x-RlD-.js.gz +0 -0
  55. package/dist/client/assets/{chunk-JWPE2WC7-DnbB0Lhi.js → chunk-JWPE2WC7-BO2gECxo.js} +1 -1
  56. package/dist/client/assets/{chunk-POPQ4Y6H-TXREax5x.js → chunk-POPQ4Y6H-CTJ8R53n.js} +1 -1
  57. package/dist/client/assets/{chunk-SVP7TREG-qPD0Q74z.js → chunk-SVP7TREG-jDMFhXej.js} +1 -1
  58. package/dist/client/assets/chunk-SVP7TREG-jDMFhXej.js.br +0 -0
  59. package/dist/client/assets/chunk-SVP7TREG-jDMFhXej.js.gz +0 -0
  60. package/dist/client/assets/{chunk-TICWLB2K-yzLwt_MH.js → chunk-TICWLB2K-DuOVjlA7.js} +1 -1
  61. package/dist/client/assets/chunk-TICWLB2K-DuOVjlA7.js.br +0 -0
  62. package/dist/client/assets/chunk-TICWLB2K-DuOVjlA7.js.gz +0 -0
  63. package/dist/client/assets/{chunk-XXDRQBXY-Dksq2NzZ.js → chunk-XXDRQBXY-Dg68j1ru.js} +1 -1
  64. package/dist/client/assets/classDiagram-ZZMXUADV-BxAMJQEG.js +1 -0
  65. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BxAMJQEG.js +1 -0
  66. package/dist/client/assets/{cose-bilkent-JH36ORCC-BZyAGO6R.js → cose-bilkent-JH36ORCC-eC0PV-vF.js} +1 -1
  67. package/dist/client/assets/cose-bilkent-JH36ORCC-eC0PV-vF.js.br +0 -0
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-eC0PV-vF.js.gz +0 -0
  69. package/dist/client/assets/{cynefin-OW5HDTMX-CyrkJWSU.js → cynefin-OW5HDTMX-CbwuuKCK.js} +1 -1
  70. package/dist/client/assets/cynefin-OW5HDTMX-CbwuuKCK.js.br +0 -0
  71. package/dist/client/assets/cynefin-OW5HDTMX-CbwuuKCK.js.gz +0 -0
  72. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BrqS8ZGX.js → cynefinDiagram-5FMLGOSQ-DiL62xjJ.js} +1 -1
  73. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DiL62xjJ.js.br +0 -0
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DiL62xjJ.js.gz +0 -0
  75. package/dist/client/assets/{dagre-GXQ25YYZ-ysmdkSNr.js → dagre-GXQ25YYZ-BoCcQeIF.js} +1 -1
  76. package/dist/client/assets/dagre-GXQ25YYZ-BoCcQeIF.js.br +0 -0
  77. package/dist/client/assets/dagre-GXQ25YYZ-BoCcQeIF.js.gz +0 -0
  78. package/dist/client/assets/{diagram-S7CK7UJ4-CWSuzX1d.js → diagram-S7CK7UJ4-BHxSkDxD.js} +1 -1
  79. package/dist/client/assets/diagram-S7CK7UJ4-BHxSkDxD.js.br +0 -0
  80. package/dist/client/assets/diagram-S7CK7UJ4-BHxSkDxD.js.gz +0 -0
  81. package/dist/client/assets/{diagram-UQ7AKVKN-BMxc5NDd.js → diagram-UQ7AKVKN-D2t82qXI.js} +1 -1
  82. package/dist/client/assets/diagram-UQ7AKVKN-D2t82qXI.js.br +0 -0
  83. package/dist/client/assets/diagram-UQ7AKVKN-D2t82qXI.js.gz +0 -0
  84. package/dist/client/assets/{diagram-VSXAHHWV-Cz5KpnGX.js → diagram-VSXAHHWV-DYQmQwL5.js} +1 -1
  85. package/dist/client/assets/diagram-VSXAHHWV-DYQmQwL5.js.br +0 -0
  86. package/dist/client/assets/diagram-VSXAHHWV-DYQmQwL5.js.gz +0 -0
  87. package/dist/client/assets/{diagram-VX7I27RA-CNk1h20b.js → diagram-VX7I27RA-6SfVR4Z9.js} +1 -1
  88. package/dist/client/assets/diagram-VX7I27RA-6SfVR4Z9.js.br +0 -0
  89. package/dist/client/assets/diagram-VX7I27RA-6SfVR4Z9.js.gz +0 -0
  90. package/dist/client/assets/{diagram-Z3DM3KII-C4lcgl6z.js → diagram-Z3DM3KII-CQiCDGAx.js} +1 -1
  91. package/dist/client/assets/diagram-Z3DM3KII-CQiCDGAx.js.br +0 -0
  92. package/dist/client/assets/diagram-Z3DM3KII-CQiCDGAx.js.gz +0 -0
  93. package/dist/client/assets/{ebnfDiagram-PWID7BFC-Cwo356mF.js → ebnfDiagram-PWID7BFC-C7YMHrC7.js} +1 -1
  94. package/dist/client/assets/ebnfDiagram-PWID7BFC-C7YMHrC7.js.br +0 -0
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-C7YMHrC7.js.gz +0 -0
  96. package/dist/client/assets/{erDiagram-RLTQ6QDP-B4DEocxu.js → erDiagram-RLTQ6QDP-C1-5SbSU.js} +1 -1
  97. package/dist/client/assets/erDiagram-RLTQ6QDP-C1-5SbSU.js.br +0 -0
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-C1-5SbSU.js.gz +0 -0
  99. package/dist/client/assets/{flowDiagram-HODETNUW-CWP3R7jz.js → flowDiagram-HODETNUW-xNfUeMOl.js} +1 -1
  100. package/dist/client/assets/flowDiagram-HODETNUW-xNfUeMOl.js.br +0 -0
  101. package/dist/client/assets/flowDiagram-HODETNUW-xNfUeMOl.js.gz +0 -0
  102. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-DBdMWa6L.js → ganttDiagram-EL5Y4UJY-CHiEXULk.js} +1 -1
  103. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CHiEXULk.js.br +0 -0
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CHiEXULk.js.gz +0 -0
  105. package/dist/client/assets/{gestureArbiter-FNHriuhM.js → gestureArbiter-Q4qvTxJM.js} +1 -1
  106. package/dist/client/assets/gestureArbiter-Q4qvTxJM.js.br +0 -0
  107. package/dist/client/assets/gestureArbiter-Q4qvTxJM.js.gz +0 -0
  108. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-B6qG6eRT.js → gitGraphDiagram-WWUBYQGX-DGHj-HHA.js} +1 -1
  109. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DGHj-HHA.js.br +0 -0
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DGHj-HHA.js.gz +0 -0
  111. package/dist/client/assets/{index-fjWWhVQp.css → index-CwuSutCd.css} +1 -1
  112. package/dist/client/assets/index-CwuSutCd.css.br +0 -0
  113. package/dist/client/assets/{index-fjWWhVQp.css.gz → index-CwuSutCd.css.gz} +0 -0
  114. package/dist/client/assets/{infoDiagram-27XIBGKW-DStk0sCn.js → infoDiagram-27XIBGKW-CkKjaZnz.js} +1 -1
  115. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-Cz8OXUK9.js → ishikawaDiagram-5VMMS53U-BxwoaCAJ.js} +1 -1
  116. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BxwoaCAJ.js.br +0 -0
  117. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BxwoaCAJ.js.gz +0 -0
  118. package/dist/client/assets/{journeyDiagram-3NMN7TZE-CD4v2_eb.js → journeyDiagram-3NMN7TZE-Co0Qn6y1.js} +1 -1
  119. package/dist/client/assets/journeyDiagram-3NMN7TZE-Co0Qn6y1.js.br +0 -0
  120. package/dist/client/assets/journeyDiagram-3NMN7TZE-Co0Qn6y1.js.gz +0 -0
  121. package/dist/client/assets/{kanban-definition-UXKFOSKX-DwLWqTSY.js → kanban-definition-UXKFOSKX-BSOfc04T.js} +1 -1
  122. package/dist/client/assets/kanban-definition-UXKFOSKX-BSOfc04T.js.br +0 -0
  123. package/dist/client/assets/kanban-definition-UXKFOSKX-BSOfc04T.js.gz +0 -0
  124. package/dist/client/assets/{layout-C1EhlDX0.js → layout-BmHMb94h.js} +1 -1
  125. package/dist/client/assets/layout-BmHMb94h.js.br +0 -0
  126. package/dist/client/assets/layout-BmHMb94h.js.gz +0 -0
  127. package/dist/client/assets/{linear-DQLRPRnN.js → linear-y-Abd9JO.js} +1 -1
  128. package/dist/client/assets/linear-y-Abd9JO.js.br +0 -0
  129. package/dist/client/assets/linear-y-Abd9JO.js.gz +0 -0
  130. package/dist/client/assets/{mermaid.core-BzEGo4OD.js → mermaid.core-B4rzVonW.js} +5 -5
  131. package/dist/client/assets/mermaid.core-B4rzVonW.js.br +0 -0
  132. package/dist/client/assets/mermaid.core-B4rzVonW.js.gz +0 -0
  133. package/dist/client/assets/{mindmap-definition-YA3MSWOX-Dct8OiDY.js → mindmap-definition-YA3MSWOX-DY3bdl53.js} +1 -1
  134. package/dist/client/assets/mindmap-definition-YA3MSWOX-DY3bdl53.js.br +0 -0
  135. package/dist/client/assets/mindmap-definition-YA3MSWOX-DY3bdl53.js.gz +0 -0
  136. package/dist/client/assets/{passwordBootstrap-C7Gy7ATG.js → passwordBootstrap-kiljsfOw.js} +1 -1
  137. package/dist/client/assets/passwordBootstrap-kiljsfOw.js.br +0 -0
  138. package/dist/client/assets/passwordBootstrap-kiljsfOw.js.gz +0 -0
  139. package/dist/client/assets/{pegDiagram-XKGWAZYB-fIABDYj-.js → pegDiagram-XKGWAZYB-XdVcJb90.js} +1 -1
  140. package/dist/client/assets/pegDiagram-XKGWAZYB-XdVcJb90.js.br +0 -0
  141. package/dist/client/assets/pegDiagram-XKGWAZYB-XdVcJb90.js.gz +0 -0
  142. package/dist/client/assets/{pieDiagram-E7YTZNPT-D2EuLDIj.js → pieDiagram-E7YTZNPT-CgAHRPHX.js} +1 -1
  143. package/dist/client/assets/pieDiagram-E7YTZNPT-CgAHRPHX.js.br +0 -0
  144. package/dist/client/assets/pieDiagram-E7YTZNPT-CgAHRPHX.js.gz +0 -0
  145. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BjF42rwO.js → quadrantDiagram-AXDQQJYC-C-nwguRe.js} +1 -1
  146. package/dist/client/assets/quadrantDiagram-AXDQQJYC-C-nwguRe.js.br +0 -0
  147. package/dist/client/assets/quadrantDiagram-AXDQQJYC-C-nwguRe.js.gz +0 -0
  148. package/dist/client/assets/{railroadDiagram-O6MQD6OU-DjfSTDvw.js → railroadDiagram-O6MQD6OU-TcJFsMp6.js} +1 -1
  149. package/dist/client/assets/railroadDiagram-O6MQD6OU-TcJFsMp6.js.br +0 -0
  150. package/dist/client/assets/railroadDiagram-O6MQD6OU-TcJFsMp6.js.gz +0 -0
  151. package/dist/client/assets/{referenceLongPress-Cz9no5pI.js → referenceLongPress-DcD1aZqh.js} +1 -1
  152. package/dist/client/assets/referenceLongPress-DcD1aZqh.js.br +0 -0
  153. package/dist/client/assets/referenceLongPress-DcD1aZqh.js.gz +0 -0
  154. package/dist/client/assets/{requirementDiagram-BXWQKSXE-BewZ62Ou.js → requirementDiagram-BXWQKSXE-CDAoFFyT.js} +1 -1
  155. package/dist/client/assets/requirementDiagram-BXWQKSXE-CDAoFFyT.js.br +0 -0
  156. package/dist/client/assets/requirementDiagram-BXWQKSXE-CDAoFFyT.js.gz +0 -0
  157. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-BhlchAkm.js → sankeyDiagram-P5KCCOFB-rscTAtOd.js} +1 -1
  158. package/dist/client/assets/sankeyDiagram-P5KCCOFB-rscTAtOd.js.br +0 -0
  159. package/dist/client/assets/sankeyDiagram-P5KCCOFB-rscTAtOd.js.gz +0 -0
  160. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CFzratAc.js → sequenceDiagram-WJ2MYXX4-w_pdEULi.js} +1 -1
  161. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-w_pdEULi.js.br +0 -0
  162. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-w_pdEULi.js.gz +0 -0
  163. package/dist/client/assets/{sizeCapture-INFHLROL-DeLB3S5x.js → sizeCapture-INFHLROL-tqubtbTJ.js} +1 -1
  164. package/dist/client/assets/sizeCapture-INFHLROL-tqubtbTJ.js.br +0 -0
  165. package/dist/client/assets/sizeCapture-INFHLROL-tqubtbTJ.js.gz +0 -0
  166. package/dist/client/assets/{sparkles-BHsBDSo5.js → sparkles-BQVhXL1X.js} +1 -1
  167. package/dist/client/assets/{stateDiagram-D77RDMKH-smIkttxr.js → stateDiagram-D77RDMKH-DFm71XIp.js} +1 -1
  168. package/dist/client/assets/stateDiagram-D77RDMKH-DFm71XIp.js.br +0 -0
  169. package/dist/client/assets/stateDiagram-D77RDMKH-DFm71XIp.js.gz +0 -0
  170. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-aTUiN9r3.js +1 -0
  171. package/dist/client/assets/{swimlanes-42K2YHIH-CVwGuPcK.js → swimlanes-42K2YHIH-Bg6TJPYj.js} +1 -1
  172. package/dist/client/assets/swimlanes-42K2YHIH-Bg6TJPYj.js.br +0 -0
  173. package/dist/client/assets/swimlanes-42K2YHIH-Bg6TJPYj.js.gz +0 -0
  174. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-Bwf9yeLM.js +8 -0
  175. package/dist/client/assets/{timeline-definition-24CTP7MA-Dsn_Rxbd.js → timeline-definition-24CTP7MA-CwrKihuj.js} +1 -1
  176. package/dist/client/assets/timeline-definition-24CTP7MA-CwrKihuj.js.br +0 -0
  177. package/dist/client/assets/timeline-definition-24CTP7MA-CwrKihuj.js.gz +0 -0
  178. package/dist/client/assets/{triangle-alert-DexJX8jR.js → triangle-alert-DZyRaNWe.js} +1 -1
  179. package/dist/client/assets/{vennDiagram-4TSXK5OY-Dtdth9UN.js → vennDiagram-4TSXK5OY-WqUYcege.js} +1 -1
  180. package/dist/client/assets/vennDiagram-4TSXK5OY-WqUYcege.js.br +0 -0
  181. package/dist/client/assets/vennDiagram-4TSXK5OY-WqUYcege.js.gz +0 -0
  182. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-82oGoyOd.js → wardleyDiagram-VM6X3IG4-B7Xfv-yN.js} +1 -1
  183. package/dist/client/assets/wardleyDiagram-VM6X3IG4-B7Xfv-yN.js.br +0 -0
  184. package/dist/client/assets/wardleyDiagram-VM6X3IG4-B7Xfv-yN.js.gz +0 -0
  185. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Ygu0NbUo.js → xychartDiagram-S5SC5T6Z-BJupQX5U.js} +1 -1
  186. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Ygu0NbUo.js.br → xychartDiagram-S5SC5T6Z-BJupQX5U.js.br} +0 -0
  187. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BJupQX5U.js.gz +0 -0
  188. package/dist/client/index.html +2 -2
  189. package/dist/client/index.html.br +0 -0
  190. package/dist/client/index.html.gz +0 -0
  191. package/dist/client/sw.js +1 -1
  192. package/dist/client/sw.js.br +0 -0
  193. package/dist/client/sw.js.gz +0 -0
  194. package/dist/server/android/adb.js +154 -0
  195. package/dist/server/android/scrcpy.js +379 -0
  196. package/dist/server/entry.js +9 -1
  197. package/dist/server/federation/runtime.js +29 -4
  198. package/dist/server/routes/android.js +205 -0
  199. package/dist/server/routes/terminal.js +13 -1
  200. package/dist/server/utils/apiAccess.js +3 -1
  201. package/dist/server/utils/settings.js +47 -0
  202. package/package.json +1 -1
  203. package/runtime-manifest.json +3 -3
  204. package/dist/client/assets/App-DA7cQLR7.js.br +0 -0
  205. package/dist/client/assets/App-DA7cQLR7.js.gz +0 -0
  206. package/dist/client/assets/ChangeWalkthroughPanel-vDiLbIdv.js.br +0 -0
  207. package/dist/client/assets/ChangeWalkthroughPanel-vDiLbIdv.js.gz +0 -0
  208. package/dist/client/assets/DagPlayground-BvY_KblL.js.br +0 -0
  209. package/dist/client/assets/DagPlayground-BvY_KblL.js.gz +0 -0
  210. package/dist/client/assets/DiffLab-Dra7cEKx.js.br +0 -0
  211. package/dist/client/assets/DiffLab-Dra7cEKx.js.gz +0 -0
  212. package/dist/client/assets/DiffReview-v9_1IS2n.js.br +0 -0
  213. package/dist/client/assets/DiffReview-v9_1IS2n.js.gz +0 -0
  214. package/dist/client/assets/DiffReviewLab-BF8Dada6.js.br +0 -0
  215. package/dist/client/assets/DiffReviewLab-BF8Dada6.js.gz +0 -0
  216. package/dist/client/assets/DiffViewer-C2s4BXp0.js.br +0 -0
  217. package/dist/client/assets/DiffViewer-C2s4BXp0.js.gz +0 -0
  218. package/dist/client/assets/ModelPreview-D9pzW_YD.js.br +0 -0
  219. package/dist/client/assets/ModelPreview-D9pzW_YD.js.gz +0 -0
  220. package/dist/client/assets/RightSidebar-DL8z6vvJ.js +0 -1678
  221. package/dist/client/assets/RightSidebar-DL8z6vvJ.js.br +0 -0
  222. package/dist/client/assets/RightSidebar-DL8z6vvJ.js.gz +0 -0
  223. package/dist/client/assets/abnfDiagram-VCTEODGH-DVOxqZDY.js.br +0 -0
  224. package/dist/client/assets/abnfDiagram-VCTEODGH-DVOxqZDY.js.gz +0 -0
  225. package/dist/client/assets/app-JK4pM9zv.js.br +0 -0
  226. package/dist/client/assets/app-JK4pM9zv.js.gz +0 -0
  227. package/dist/client/assets/arc-DY_WK1Ln.js.br +0 -0
  228. package/dist/client/assets/arc-DY_WK1Ln.js.gz +0 -0
  229. package/dist/client/assets/architectureDiagram-5GKGNRK7-oyhVtQ5E.js.br +0 -0
  230. package/dist/client/assets/blockDiagram-I7D4REHJ-DTMir8MU.js.br +0 -0
  231. package/dist/client/assets/blockDiagram-I7D4REHJ-DTMir8MU.js.gz +0 -0
  232. package/dist/client/assets/c4Diagram-7LVT6UL2-DpDHYMUW.js.br +0 -0
  233. package/dist/client/assets/c4Diagram-7LVT6UL2-DpDHYMUW.js.gz +0 -0
  234. package/dist/client/assets/channel-cexw6K2f.js +0 -1
  235. package/dist/client/assets/chunk-F27PBJKO-8wDqj2MV.js.br +0 -0
  236. package/dist/client/assets/chunk-F27PBJKO-8wDqj2MV.js.gz +0 -0
  237. package/dist/client/assets/chunk-IMKFNOWR-BnRNosQc.js.br +0 -0
  238. package/dist/client/assets/chunk-IMKFNOWR-BnRNosQc.js.gz +0 -0
  239. package/dist/client/assets/chunk-SVP7TREG-qPD0Q74z.js.br +0 -0
  240. package/dist/client/assets/chunk-SVP7TREG-qPD0Q74z.js.gz +0 -0
  241. package/dist/client/assets/chunk-TICWLB2K-yzLwt_MH.js.br +0 -0
  242. package/dist/client/assets/chunk-TICWLB2K-yzLwt_MH.js.gz +0 -0
  243. package/dist/client/assets/classDiagram-ZZMXUADV-CpjKakYG.js +0 -1
  244. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CpjKakYG.js +0 -1
  245. package/dist/client/assets/cose-bilkent-JH36ORCC-BZyAGO6R.js.br +0 -0
  246. package/dist/client/assets/cose-bilkent-JH36ORCC-BZyAGO6R.js.gz +0 -0
  247. package/dist/client/assets/cynefin-OW5HDTMX-CyrkJWSU.js.br +0 -0
  248. package/dist/client/assets/cynefin-OW5HDTMX-CyrkJWSU.js.gz +0 -0
  249. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BrqS8ZGX.js.br +0 -0
  250. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BrqS8ZGX.js.gz +0 -0
  251. package/dist/client/assets/dagre-GXQ25YYZ-ysmdkSNr.js.br +0 -0
  252. package/dist/client/assets/dagre-GXQ25YYZ-ysmdkSNr.js.gz +0 -0
  253. package/dist/client/assets/diagram-S7CK7UJ4-CWSuzX1d.js.br +0 -0
  254. package/dist/client/assets/diagram-S7CK7UJ4-CWSuzX1d.js.gz +0 -0
  255. package/dist/client/assets/diagram-UQ7AKVKN-BMxc5NDd.js.br +0 -0
  256. package/dist/client/assets/diagram-UQ7AKVKN-BMxc5NDd.js.gz +0 -0
  257. package/dist/client/assets/diagram-VSXAHHWV-Cz5KpnGX.js.br +0 -0
  258. package/dist/client/assets/diagram-VSXAHHWV-Cz5KpnGX.js.gz +0 -0
  259. package/dist/client/assets/diagram-VX7I27RA-CNk1h20b.js.br +0 -0
  260. package/dist/client/assets/diagram-VX7I27RA-CNk1h20b.js.gz +0 -0
  261. package/dist/client/assets/diagram-Z3DM3KII-C4lcgl6z.js.br +0 -0
  262. package/dist/client/assets/diagram-Z3DM3KII-C4lcgl6z.js.gz +0 -0
  263. package/dist/client/assets/ebnfDiagram-PWID7BFC-Cwo356mF.js.br +0 -0
  264. package/dist/client/assets/ebnfDiagram-PWID7BFC-Cwo356mF.js.gz +0 -0
  265. package/dist/client/assets/erDiagram-RLTQ6QDP-B4DEocxu.js.br +0 -0
  266. package/dist/client/assets/erDiagram-RLTQ6QDP-B4DEocxu.js.gz +0 -0
  267. package/dist/client/assets/flowDiagram-HODETNUW-CWP3R7jz.js.br +0 -0
  268. package/dist/client/assets/flowDiagram-HODETNUW-CWP3R7jz.js.gz +0 -0
  269. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DBdMWa6L.js.br +0 -0
  270. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DBdMWa6L.js.gz +0 -0
  271. package/dist/client/assets/gestureArbiter-FNHriuhM.js.br +0 -0
  272. package/dist/client/assets/gestureArbiter-FNHriuhM.js.gz +0 -0
  273. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-B6qG6eRT.js.br +0 -0
  274. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-B6qG6eRT.js.gz +0 -0
  275. package/dist/client/assets/index-fjWWhVQp.css.br +0 -0
  276. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cz8OXUK9.js.br +0 -0
  277. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cz8OXUK9.js.gz +0 -0
  278. package/dist/client/assets/journeyDiagram-3NMN7TZE-CD4v2_eb.js.br +0 -0
  279. package/dist/client/assets/journeyDiagram-3NMN7TZE-CD4v2_eb.js.gz +0 -0
  280. package/dist/client/assets/kanban-definition-UXKFOSKX-DwLWqTSY.js.br +0 -0
  281. package/dist/client/assets/kanban-definition-UXKFOSKX-DwLWqTSY.js.gz +0 -0
  282. package/dist/client/assets/layout-C1EhlDX0.js.br +0 -0
  283. package/dist/client/assets/layout-C1EhlDX0.js.gz +0 -0
  284. package/dist/client/assets/linear-DQLRPRnN.js.br +0 -0
  285. package/dist/client/assets/linear-DQLRPRnN.js.gz +0 -0
  286. package/dist/client/assets/mermaid.core-BzEGo4OD.js.br +0 -0
  287. package/dist/client/assets/mermaid.core-BzEGo4OD.js.gz +0 -0
  288. package/dist/client/assets/mindmap-definition-YA3MSWOX-Dct8OiDY.js.br +0 -0
  289. package/dist/client/assets/mindmap-definition-YA3MSWOX-Dct8OiDY.js.gz +0 -0
  290. package/dist/client/assets/passwordBootstrap-C7Gy7ATG.js.br +0 -0
  291. package/dist/client/assets/passwordBootstrap-C7Gy7ATG.js.gz +0 -0
  292. package/dist/client/assets/pegDiagram-XKGWAZYB-fIABDYj-.js.br +0 -0
  293. package/dist/client/assets/pegDiagram-XKGWAZYB-fIABDYj-.js.gz +0 -0
  294. package/dist/client/assets/pieDiagram-E7YTZNPT-D2EuLDIj.js.br +0 -0
  295. package/dist/client/assets/pieDiagram-E7YTZNPT-D2EuLDIj.js.gz +0 -0
  296. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BjF42rwO.js.br +0 -0
  297. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BjF42rwO.js.gz +0 -0
  298. package/dist/client/assets/railroadDiagram-O6MQD6OU-DjfSTDvw.js.br +0 -0
  299. package/dist/client/assets/railroadDiagram-O6MQD6OU-DjfSTDvw.js.gz +0 -0
  300. package/dist/client/assets/referenceLongPress-Cz9no5pI.js.br +0 -0
  301. package/dist/client/assets/referenceLongPress-Cz9no5pI.js.gz +0 -0
  302. package/dist/client/assets/requirementDiagram-BXWQKSXE-BewZ62Ou.js.br +0 -0
  303. package/dist/client/assets/requirementDiagram-BXWQKSXE-BewZ62Ou.js.gz +0 -0
  304. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BhlchAkm.js.br +0 -0
  305. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BhlchAkm.js.gz +0 -0
  306. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CFzratAc.js.br +0 -0
  307. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CFzratAc.js.gz +0 -0
  308. package/dist/client/assets/sizeCapture-INFHLROL-DeLB3S5x.js.br +0 -0
  309. package/dist/client/assets/sizeCapture-INFHLROL-DeLB3S5x.js.gz +0 -0
  310. package/dist/client/assets/stateDiagram-D77RDMKH-smIkttxr.js.br +0 -0
  311. package/dist/client/assets/stateDiagram-D77RDMKH-smIkttxr.js.gz +0 -0
  312. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BFDLILuN.js +0 -1
  313. package/dist/client/assets/swimlanes-42K2YHIH-CVwGuPcK.js.br +0 -0
  314. package/dist/client/assets/swimlanes-42K2YHIH-CVwGuPcK.js.gz +0 -0
  315. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-B0Dzvkwg.js +0 -8
  316. package/dist/client/assets/timeline-definition-24CTP7MA-Dsn_Rxbd.js.br +0 -0
  317. package/dist/client/assets/timeline-definition-24CTP7MA-Dsn_Rxbd.js.gz +0 -0
  318. package/dist/client/assets/vennDiagram-4TSXK5OY-Dtdth9UN.js.br +0 -0
  319. package/dist/client/assets/vennDiagram-4TSXK5OY-Dtdth9UN.js.gz +0 -0
  320. package/dist/client/assets/wardleyDiagram-VM6X3IG4-82oGoyOd.js.br +0 -0
  321. package/dist/client/assets/wardleyDiagram-VM6X3IG4-82oGoyOd.js.gz +0 -0
  322. package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ygu0NbUo.js.gz +0 -0
@@ -0,0 +1,379 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, realpathSync } from 'node:fs';
3
+ import net from 'node:net';
4
+ import { delimiter, dirname, join } from 'node:path';
5
+ import { randomInt } from 'node:crypto';
6
+ import { adbSearchPath, resolveAdbBinary } from './adb.js';
7
+ const DEVICE_NAME_LENGTH = 64;
8
+ const VIDEO_HEADER_LENGTH = 12;
9
+ const FRAME_HEADER_LENGTH = 12;
10
+ const CONNECT_TIMEOUT_MS = 12_000;
11
+ const CODEC_IDS = {
12
+ 0x68323634: 'h264',
13
+ 0x68323635: 'h265',
14
+ 0x00617631: 'av1',
15
+ };
16
+ function scrcpyExecutableCandidates() {
17
+ const override = process.env.TERMDOCK_SCRCPY_BIN;
18
+ const candidates = [];
19
+ if (override)
20
+ candidates.push(override);
21
+ const names = process.platform === 'win32' ? ['scrcpy.exe', 'scrcpy'] : ['scrcpy'];
22
+ // 用平台路径分隔符切分;Windows 盘符里的 ':' 在正斜杠分隔下会出错。
23
+ for (const dir of adbSearchPath().split(delimiter).filter(Boolean)) {
24
+ for (const name of names)
25
+ candidates.push(join(dir, name));
26
+ }
27
+ return candidates;
28
+ }
29
+ function resolveScrcpyBin() {
30
+ for (const candidate of scrcpyExecutableCandidates())
31
+ if (existsSync(candidate))
32
+ return candidate;
33
+ return null;
34
+ }
35
+ function serverJarCandidates(bin) {
36
+ const candidates = [process.env.TERMDOCK_SCRCPY_SERVER || ''];
37
+ if (bin !== 'scrcpy') {
38
+ let resolved = bin;
39
+ try {
40
+ resolved = realpathSync(bin);
41
+ }
42
+ catch { /* keep original path */ }
43
+ const dir = dirname(resolved);
44
+ candidates.push(join(dir, 'scrcpy-server'));
45
+ candidates.push(join(dir, '..', 'share', 'scrcpy', 'scrcpy-server'));
46
+ }
47
+ candidates.push('/usr/local/share/scrcpy/scrcpy-server');
48
+ candidates.push('/usr/share/scrcpy/scrcpy-server');
49
+ candidates.push('/opt/homebrew/share/scrcpy/scrcpy-server');
50
+ return candidates.filter(Boolean);
51
+ }
52
+ let cachedBinaries;
53
+ function runCapture(file, args, timeout) {
54
+ return new Promise((resolve, reject) => {
55
+ const child = spawn(file, args, { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, PATH: adbSearchPath() } });
56
+ let out = '', err = '';
57
+ const timer = setTimeout(() => { child.kill('SIGKILL'); reject(new Error('SCRCPY_PROBE_TIMEOUT')); }, timeout);
58
+ child.stdout.on('data', chunk => { out += chunk; });
59
+ child.stderr.on('data', chunk => { err += chunk; });
60
+ child.on('error', error => { clearTimeout(timer); reject(error); });
61
+ child.on('close', code => {
62
+ clearTimeout(timer);
63
+ if (code === 0 || out.trim())
64
+ resolve(out.trim() || err.trim());
65
+ else
66
+ reject(new Error(err.trim() || `SCRCPY_PROBE_FAILED_${code}`));
67
+ });
68
+ });
69
+ }
70
+ /** 定位 scrcpy 客户端与随包 scrcpy-server,并从 `scrcpy --version` 取协议版本。 */
71
+ export async function resolveScrcpyBinaries() {
72
+ if (cachedBinaries !== undefined) {
73
+ if (!cachedBinaries)
74
+ throw new Error('SCRCPY_NOT_FOUND');
75
+ return cachedBinaries;
76
+ }
77
+ // 找不到绝对路径时退回 PATH 解析(安装包可能把 scrcpy 放在非常规目录)。
78
+ const bin = resolveScrcpyBin() ?? 'scrcpy';
79
+ const serverJar = serverJarCandidates(bin).find(candidate => existsSync(candidate));
80
+ if (!serverJar) {
81
+ cachedBinaries = null;
82
+ throw new Error('SCRCPY_SERVER_NOT_FOUND');
83
+ }
84
+ let version = '';
85
+ try {
86
+ const output = await runCapture(bin, ['--version'], 6000);
87
+ version = /^scrcpy\s+([0-9][^\s]*)/m.exec(output)?.[1] ?? '';
88
+ }
89
+ catch {
90
+ version = '';
91
+ }
92
+ if (!version) {
93
+ cachedBinaries = null;
94
+ throw new Error('SCRCPY_VERSION_UNKNOWN');
95
+ }
96
+ cachedBinaries = { bin, serverJar, version };
97
+ return cachedBinaries;
98
+ }
99
+ function remoteJarPath(version) {
100
+ return `/data/local/tmp/termdock-scrcpy-server-${version}.jar`;
101
+ }
102
+ function adbBinary() {
103
+ const binary = resolveAdbBinary();
104
+ if (!binary)
105
+ throw new Error('ADB_NOT_FOUND');
106
+ return binary;
107
+ }
108
+ function runAdbCapture(args, timeout = 30_000) {
109
+ return new Promise((resolve, reject) => {
110
+ const child = spawn(adbBinary(), args, { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, PATH: adbSearchPath() } });
111
+ let out = '', err = '';
112
+ const timer = setTimeout(() => { child.kill('SIGKILL'); reject(new Error('ADB_TIMEOUT')); }, timeout);
113
+ child.on('error', error => { clearTimeout(timer); reject(error); });
114
+ child.stdout.on('data', chunk => { out += chunk; });
115
+ child.stderr.on('data', chunk => { err += chunk; });
116
+ child.on('close', code => {
117
+ clearTimeout(timer);
118
+ if (code === 0)
119
+ resolve(out.trim());
120
+ else
121
+ reject(new Error((err || out).trim() || `ADB_FAILED_${code}`));
122
+ });
123
+ });
124
+ }
125
+ function scidHex(scid) {
126
+ return scid.toString(16).padStart(8, '0');
127
+ }
128
+ /** 单设备的一条 scrcpy 会话:推送 server → adb reverse → 启动 server → 解析 H.264/H.265 流。 */
129
+ export class ScrcpySession {
130
+ serial;
131
+ events;
132
+ options;
133
+ scid = randomInt(1, 0x7fffffff);
134
+ child = null;
135
+ server = null;
136
+ videoSocket = null;
137
+ controlSocket = null;
138
+ videoBuffer = Buffer.alloc(0);
139
+ controlBuffer = Buffer.alloc(0);
140
+ videoStage = 'meta';
141
+ port = 0;
142
+ stopped = false;
143
+ started = false;
144
+ deviceName = '';
145
+ resolveHeader = null;
146
+ logLines = [];
147
+ constructor(serial, events, options = {}) {
148
+ this.serial = serial;
149
+ this.events = events;
150
+ this.options = options;
151
+ }
152
+ get running() { return this.started && !this.stopped; }
153
+ log(line) {
154
+ const text = line.trim();
155
+ if (!text)
156
+ return;
157
+ this.logLines.push(text);
158
+ if (this.logLines.length > 40)
159
+ this.logLines.shift();
160
+ }
161
+ fail(error) {
162
+ if (this.stopped)
163
+ return;
164
+ this.events.onError(error);
165
+ void this.stop();
166
+ }
167
+ async start() {
168
+ if (this.started)
169
+ return;
170
+ this.started = true;
171
+ try {
172
+ const { serverJar, version } = await resolveScrcpyBinaries();
173
+ const remoteJar = remoteJarPath(version);
174
+ await runAdbCapture(['-s', this.serial, 'push', serverJar, remoteJar], 60_000);
175
+ const server = net.createServer();
176
+ this.server = server;
177
+ server.on('connection', socket => this.acceptSocket(socket));
178
+ await new Promise((resolve, reject) => {
179
+ server.once('error', reject);
180
+ server.listen(0, '127.0.0.1', () => { this.port = server.address().port; resolve(); });
181
+ });
182
+ await runAdbCapture(['-s', this.serial, 'reverse', `localabstract:scrcpy_${scidHex(this.scid)}`, `tcp:${this.port}`], 15_000);
183
+ const args = [
184
+ '-s', this.serial, 'shell',
185
+ `CLASSPATH=${remoteJar}`,
186
+ 'app_process', '/',
187
+ 'com.genymobile.scrcpy.Server', version,
188
+ `scid=${this.scid.toString(16)}`,
189
+ 'log_level=info',
190
+ 'audio=false',
191
+ 'control=true',
192
+ 'tunnel_forward=false',
193
+ 'power_on=true',
194
+ 'stay_awake=true',
195
+ 'clipboard_autosync=false',
196
+ `max_size=${this.options.maxSize ?? 1600}`,
197
+ `video_bit_rate=${this.options.videoBitRate ?? 8_000_000}`,
198
+ ];
199
+ if (this.options.maxFps && this.options.maxFps > 0)
200
+ args.push(`max_fps=${this.options.maxFps}`);
201
+ const child = spawn(adbBinary(), args, { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, PATH: adbSearchPath() } });
202
+ this.child = child;
203
+ child.stdout?.on('data', chunk => this.log(String(chunk).replace(/^\[server\]\s*/, '')));
204
+ child.stderr?.on('data', chunk => this.log(String(chunk)));
205
+ child.on('error', error => this.fail(error instanceof Error ? error : new Error('SCRCPY_SPAWN_FAILED')));
206
+ child.on('close', () => { if (!this.stopped)
207
+ this.fail(new Error(this.logLines.at(-1) || 'SCRCPY_SERVER_EXITED')); });
208
+ await this.waitForHeader();
209
+ }
210
+ catch (error) {
211
+ const normalized = error instanceof Error ? error : new Error('SCRCPY_START_FAILED');
212
+ if (!this.stopped)
213
+ this.fail(normalized);
214
+ throw normalized;
215
+ }
216
+ }
217
+ waitForHeader() {
218
+ return new Promise((resolve, reject) => {
219
+ const timer = setTimeout(() => { this.resolveHeader = null; reject(new Error('SCRCPY_CONNECT_TIMEOUT')); }, CONNECT_TIMEOUT_MS);
220
+ this.resolveHeader = () => { clearTimeout(timer); this.resolveHeader = null; resolve(); };
221
+ });
222
+ }
223
+ acceptSocket(socket) {
224
+ socket.setNoDelay(true);
225
+ if (!this.videoSocket) {
226
+ this.videoSocket = socket;
227
+ socket.on('data', chunk => this.readVideo(chunk));
228
+ socket.on('error', error => this.fail(error instanceof Error ? error : new Error('VIDEO_SOCKET_ERROR')));
229
+ socket.on('close', () => { if (!this.stopped)
230
+ this.fail(new Error(this.logLines.at(-1) || 'VIDEO_SOCKET_CLOSED')); });
231
+ return;
232
+ }
233
+ if (!this.controlSocket) {
234
+ this.controlSocket = socket;
235
+ // 控制通道会回传剪贴板/ACK 等设备消息;v1 只消费,避免读缓冲堆积。
236
+ socket.on('data', chunk => this.readControl(chunk));
237
+ socket.on('error', () => { });
238
+ return;
239
+ }
240
+ socket.destroy();
241
+ }
242
+ readVideo(chunk) {
243
+ if (this.stopped)
244
+ return;
245
+ this.videoBuffer = Buffer.concat([this.videoBuffer, chunk]);
246
+ if (this.videoStage === 'meta') {
247
+ if (this.videoBuffer.length < DEVICE_NAME_LENGTH)
248
+ return;
249
+ this.deviceName = this.videoBuffer.subarray(0, DEVICE_NAME_LENGTH).toString('utf8').replace(/\0+$/, '');
250
+ this.videoBuffer = this.videoBuffer.subarray(DEVICE_NAME_LENGTH);
251
+ this.videoStage = 'codec';
252
+ }
253
+ if (this.videoStage === 'codec') {
254
+ if (this.videoBuffer.length < VIDEO_HEADER_LENGTH)
255
+ return;
256
+ const codec = CODEC_IDS[this.videoBuffer.readUInt32BE(0)];
257
+ const width = this.videoBuffer.readUInt32BE(4);
258
+ const height = this.videoBuffer.readUInt32BE(8);
259
+ this.videoBuffer = this.videoBuffer.subarray(VIDEO_HEADER_LENGTH);
260
+ this.videoStage = 'frames';
261
+ if (!codec) {
262
+ this.fail(new Error('SCRCPY_UNSUPPORTED_CODEC'));
263
+ return;
264
+ }
265
+ this.events.onHeader({ codec, width, height, deviceName: this.deviceName || this.serial });
266
+ this.resolveHeader?.();
267
+ }
268
+ while (this.videoStage === 'frames' && this.videoBuffer.length >= FRAME_HEADER_LENGTH) {
269
+ const ptsFlags = this.videoBuffer.readBigUInt64BE(0);
270
+ const size = this.videoBuffer.readUInt32BE(8);
271
+ if (this.videoBuffer.length < FRAME_HEADER_LENGTH + size)
272
+ break;
273
+ const payload = Buffer.from(this.videoBuffer.subarray(FRAME_HEADER_LENGTH, FRAME_HEADER_LENGTH + size));
274
+ this.videoBuffer = this.videoBuffer.subarray(FRAME_HEADER_LENGTH + size);
275
+ const config = ((ptsFlags >> 63n) & 1n) === 1n;
276
+ const keyFrame = ((ptsFlags >> 62n) & 1n) === 1n;
277
+ this.events.onFrame({ config, keyFrame, pts: ptsFlags & ((1n << 62n) - 1n), data: payload });
278
+ }
279
+ }
280
+ readControl(chunk) {
281
+ this.controlBuffer = Buffer.concat([this.controlBuffer, chunk]);
282
+ while (this.controlBuffer.length > 0) {
283
+ const type = this.controlBuffer[0];
284
+ if (type === 0) {
285
+ if (this.controlBuffer.length < 5)
286
+ return;
287
+ const length = this.controlBuffer.readUInt32BE(1);
288
+ if (this.controlBuffer.length < 5 + length)
289
+ return;
290
+ this.controlBuffer = this.controlBuffer.subarray(5 + length);
291
+ }
292
+ else if (type === 1) {
293
+ if (this.controlBuffer.length < 9)
294
+ return;
295
+ this.controlBuffer = this.controlBuffer.subarray(9);
296
+ }
297
+ else {
298
+ this.controlBuffer = Buffer.alloc(0);
299
+ return;
300
+ }
301
+ }
302
+ }
303
+ /** 客户端消费不过来时暂停视频 socket:让内核缓冲填满,把反压传到设备端,
304
+ * 从而在不丢帧的前提下限制延迟(丢 delta 会让参考帧链断裂、画面花屏)。 */
305
+ pauseVideo() {
306
+ if (this.videoPaused)
307
+ return;
308
+ this.videoPaused = true;
309
+ try {
310
+ this.videoSocket?.pause();
311
+ }
312
+ catch { /* ignore */ }
313
+ }
314
+ resumeVideo() {
315
+ if (!this.videoPaused)
316
+ return;
317
+ this.videoPaused = false;
318
+ try {
319
+ this.videoSocket?.resume();
320
+ }
321
+ catch { /* ignore */ }
322
+ }
323
+ videoPaused = false;
324
+ sendControl(data) {
325
+ const socket = this.controlSocket;
326
+ if (!socket || socket.destroyed)
327
+ return false;
328
+ try {
329
+ socket.write(data);
330
+ return true;
331
+ }
332
+ catch {
333
+ return false;
334
+ }
335
+ }
336
+ async stop() {
337
+ if (this.stopped)
338
+ return;
339
+ this.stopped = true;
340
+ activeSessions.delete(this);
341
+ const child = this.child;
342
+ this.child = null;
343
+ try {
344
+ this.videoSocket?.destroy();
345
+ }
346
+ catch { /* ignore */ }
347
+ try {
348
+ this.controlSocket?.destroy();
349
+ }
350
+ catch { /* ignore */ }
351
+ this.videoSocket = null;
352
+ this.controlSocket = null;
353
+ try {
354
+ this.server?.close();
355
+ }
356
+ catch { /* ignore */ }
357
+ this.server = null;
358
+ if (child) {
359
+ try {
360
+ child.kill('SIGKILL');
361
+ }
362
+ catch { /* ignore */ }
363
+ }
364
+ try {
365
+ await runAdbCapture(['-s', this.serial, 'reverse', '--remove', `localabstract:scrcpy_${scidHex(this.scid)}`], 8000);
366
+ }
367
+ catch { /* reverse 可能已随进程退出被清理 */ }
368
+ this.events.onClose();
369
+ }
370
+ }
371
+ const activeSessions = new Set();
372
+ export function createScrcpySession(serial, events, options) {
373
+ const session = new ScrcpySession(serial, events, options);
374
+ activeSessions.add(session);
375
+ return session;
376
+ }
377
+ export async function stopAllScrcpySessions() {
378
+ await Promise.all([...activeSessions].map(session => session.stop()));
379
+ }
@@ -30,6 +30,8 @@ import cookieParser from 'cookie-parser';
30
30
  import { fileURLToPath } from 'url';
31
31
  import { WebSocketServer } from 'ws';
32
32
  import terminalRoutes, { handleTerminalWebSocket, handleControlWebSocket, resolveStableFederationSessionId } from './routes/terminal.js';
33
+ import androidRoutes, { handleAndroidWebSocket } from './routes/android.js';
34
+ import { stopAllScrcpySessions } from './android/scrcpy.js';
33
35
  import filesystemRoutes from './routes/filesystem.js';
34
36
  import authRoutes from './routes/auth.js';
35
37
  import notificationRoutes from './routes/notifications.js';
@@ -336,6 +338,11 @@ export function createApp(options = {}) {
336
338
  app.use('/api/terminal', terminalRoutes);
337
339
  // 文件系统路由(继承 /api/terminal 上的 auth + CSRF 保护)
338
340
  app.use('/api/terminal/fs', filesystemRoutes);
341
+ // 本机 Android 设备桥接(adb/scrcpy)。经加密隧道时按业务 API 处理,
342
+ // 仅全权服务授权可达;投屏 WS 在 federation runtime 中按 full-service 校验。
343
+ app.use('/api/android', requireAuth({ bypass: isTrustedLocalCliRequest }));
344
+ app.use('/api/android', csrfProtection.verifyMiddleware({ bypass: isTrustedLocalCliRequest }));
345
+ app.use('/api/android', androidRoutes);
339
346
  if (fs.existsSync(bundledClientIndexPath)) {
340
347
  const selectedClientPath = resolveRuntimeClientDist(bundledClientDistPath);
341
348
  const clientPath = selectedClientPath === bundledClientDistPath
@@ -449,8 +456,9 @@ export function startServer(options = {}) {
449
456
  server.once('close', () => { clearInterval(desktopRoutesTimer); for (const target of dynamicDirectTargets.values())
450
457
  target.close(); });
451
458
  server.once('close', () => relayRouter.close());
459
+ server.once('close', () => { void stopAllScrcpySessions(); });
452
460
  const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
453
- terminal: handleTerminalWebSocket, control: handleControlWebSocket,
461
+ terminal: handleTerminalWebSocket, control: handleControlWebSocket, android: handleAndroidWebSocket,
454
462
  }, {
455
463
  resolveSessionId: resolveStableFederationSessionId,
456
464
  collaborationConnected: (subjectId, rpc) => {
@@ -252,7 +252,12 @@ export async function createFederationRuntime(app, directory, handlers, options
252
252
  }
253
253
  }
254
254
  revokeTimer = setInterval(() => {
255
- for (const { socket: logical, sessionId } of sockets.values()) {
255
+ for (const { socket: logical, sessionId, android } of sockets.values()) {
256
+ if (android) {
257
+ if (!full(subjectId))
258
+ logical.close(4003, 'Authorization revoked');
259
+ continue;
260
+ }
256
261
  if (!allowed(subjectId, sessionId ? 'session.view' : 'service.view', sessionId))
257
262
  logical.close(4003, 'Authorization revoked');
258
263
  }
@@ -558,14 +563,19 @@ export async function createFederationRuntime(app, directory, handlers, options
558
563
  throw new Error('API_NOT_ALLOWED');
559
564
  const url = new URL(packet.path, 'http://inner');
560
565
  const match = /^\/api\/terminal\/([^/%]+)\/ws$/.exec(url.pathname);
566
+ const androidMatch = /^\/api\/android\/([^/%]+)\/ws$/.exec(url.pathname);
561
567
  if (match)
562
568
  check('session.view', match[1]);
563
569
  else if (url.pathname === '/api/control/ws')
564
570
  check('service.view');
571
+ else if (androidMatch) {
572
+ if (!full(subjectId))
573
+ throw new Error('API_NOT_ALLOWED');
574
+ }
565
575
  else
566
576
  throw new Error('API_NOT_ALLOWED');
567
- const logical = new LogicalSocket(channel, packet.id, () => allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]));
568
- sockets.set(packet.id, { socket: logical, sessionId: match?.[1] });
577
+ const logical = new LogicalSocket(channel, packet.id, () => androidMatch ? full(subjectId) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]));
578
+ sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: Boolean(androidMatch) });
569
579
  logical.once('close', () => sockets.delete(packet.id));
570
580
  send({ type: 'ws-ready', id: packet.id });
571
581
  if (match)
@@ -574,6 +584,15 @@ export async function createFederationRuntime(app, directory, handlers, options
574
584
  flowControl: url.searchParams.get('flow') === '2', outputActive: url.searchParams.get('active') !== '0',
575
585
  independentTmux: full(subjectId) && url.searchParams.get('transport') === 'tmux-client',
576
586
  }, allowed(subjectId, 'session.resize', match[1]) ? readTerminalHandshakeDimensions(url.searchParams) : undefined);
587
+ else if (androidMatch) {
588
+ if (!handlers.android)
589
+ throw new Error('API_NOT_ALLOWED');
590
+ handlers.android(logical, androidMatch[1], randomUUID(), {
591
+ maxSize: Number(url.searchParams.get('max_size')) || undefined,
592
+ bitRate: Number(url.searchParams.get('bit_rate')) || undefined,
593
+ maxFps: Number(url.searchParams.get('max_fps')) || undefined,
594
+ });
595
+ }
577
596
  else
578
597
  handlers.control(logical, randomUUID());
579
598
  }
@@ -582,7 +601,13 @@ export async function createFederationRuntime(app, directory, handlers, options
582
601
  if (!item || typeof packet.data !== 'string')
583
602
  throw new Error('INVALID_STREAM');
584
603
  const data = JSON.parse(packet.data);
585
- if (item.sessionId) {
604
+ if (item.android) {
605
+ if (!full(subjectId))
606
+ throw new Error('AUTHORIZATION_DENIED');
607
+ if (!['control', 'ack', 'ping'].includes(data?.type))
608
+ throw new Error('ACTION_NOT_ALLOWED');
609
+ }
610
+ else if (item.sessionId) {
586
611
  const action = terminalMessageAction(data.type);
587
612
  if (!action)
588
613
  throw new Error('ACTION_NOT_ALLOWED');
@@ -0,0 +1,205 @@
1
+ import { Router } from 'express';
2
+ import { connectDevice, disconnectDevice, listDevices, resolveAdbBinary } from '../android/adb.js';
3
+ import { createScrcpySession, resolveScrcpyBinaries } from '../android/scrcpy.js';
4
+ const router = Router();
5
+ const SERIAL_PATTERN = /^[0-9a-zA-Z_.:\-]{1,128}$/;
6
+ /** 逐帧/逐控件的诊断日志默认关闭,需要时 TERMDOCK_ANDROID_DEBUG=1 打开。 */
7
+ const DEBUG_ANDROID = process.env.TERMDOCK_ANDROID_DEBUG === '1';
8
+ export function isValidAndroidSerial(serial) {
9
+ return SERIAL_PATTERN.test(serial);
10
+ }
11
+ router.get('/devices', async (_req, res) => {
12
+ try {
13
+ const devices = await listDevices();
14
+ let scrcpyVersion = null;
15
+ const adbAvailable = resolveAdbBinary() !== null;
16
+ try {
17
+ scrcpyVersion = (await resolveScrcpyBinaries()).version;
18
+ }
19
+ catch {
20
+ scrcpyVersion = null;
21
+ }
22
+ res.json({ adbAvailable, scrcpyVersion, devices });
23
+ }
24
+ catch (error) {
25
+ res.status(503).json({ error: error instanceof Error ? error.message : 'ADB_UNAVAILABLE', code: 'ADB_UNAVAILABLE' });
26
+ }
27
+ });
28
+ router.post('/connect', async (req, res) => {
29
+ const address = typeof req.body?.address === 'string' ? req.body.address : '';
30
+ try {
31
+ const result = await connectDevice(address);
32
+ res.json({ ok: true, serial: result.serial, output: result.output });
33
+ }
34
+ catch (error) {
35
+ const message = error instanceof Error ? error.message : 'ADB_CONNECT_FAILED';
36
+ res.status(400).json({ error: message, code: message });
37
+ }
38
+ });
39
+ router.post('/disconnect', async (req, res) => {
40
+ const address = typeof req.body?.address === 'string' ? req.body.address : '';
41
+ try {
42
+ res.json({ ok: true, output: await disconnectDevice(address) });
43
+ }
44
+ catch (error) {
45
+ const message = error instanceof Error ? error.message : 'ADB_DISCONNECT_FAILED';
46
+ res.status(400).json({ error: message, code: message });
47
+ }
48
+ });
49
+ export default router;
50
+ function clampInt(value, min, max, fallback) {
51
+ if (typeof value !== 'number' || !Number.isFinite(value))
52
+ return fallback;
53
+ return Math.max(min, Math.min(max, Math.round(value)));
54
+ }
55
+ /** 加密隧道打开 `/api/android/:serial/ws` 后的桥接:视频帧下发、控制消息回写。 */
56
+ export function handleAndroidWebSocket(socket, serial, _clientId, requested = {}) {
57
+ if (!isValidAndroidSerial(serial)) {
58
+ socket.close(4400, 'INVALID_SERIAL');
59
+ return;
60
+ }
61
+ // 网络差时客户端可主动降码率/分辨率;这里对入参做边界收敛。
62
+ const quality = {
63
+ maxSize: clampInt(requested.maxSize, 360, 2160, 1600),
64
+ videoBitRate: clampInt(requested.bitRate, 300_000, 30_000_000, 8_000_000),
65
+ maxFps: clampInt(requested.maxFps, 0, 60, 0),
66
+ };
67
+ let closed = false;
68
+ let inflight = 0;
69
+ let seq = 0;
70
+ let sent = 0;
71
+ let acked = 0;
72
+ let controlCount = 0;
73
+ let lastAckAt = 0;
74
+ let lastFrameAt = Date.now();
75
+ let videoPaused = false;
76
+ let androidSendSkipLogged = false;
77
+ let session = null;
78
+ // 客户端 ack 丢失时不能让视频永久暂停:超时视为丢包并恢复。
79
+ const flowTimer = setInterval(() => {
80
+ if (videoPaused && Date.now() - Math.max(lastAckAt, lastFrameAt) > 3000) {
81
+ if (DEBUG_ANDROID)
82
+ console.log(`[android] flow resume after ack timeout serial=${serial} inflight=${inflight} sent=${sent} acked=${acked}`);
83
+ videoPaused = false;
84
+ session?.resumeVideo();
85
+ }
86
+ }, 1000);
87
+ const statsTimer = DEBUG_ANDROID ? setInterval(() => {
88
+ if (sent > 0)
89
+ console.log(`[android] stats serial=${serial} sent=${sent} acked=${acked} inflight=${inflight} controls=${controlCount} paused=${videoPaused}`);
90
+ }, 10_000) : null;
91
+ const FRAME_WINDOW = 3;
92
+ const send = (payload) => {
93
+ if (closed || socket.readyState !== 1) {
94
+ if (!androidSendSkipLogged) {
95
+ androidSendSkipLogged = true;
96
+ console.log(`[android] send skipped serial=${serial} closed=${closed} readyState=${socket.readyState} type=${String(payload.type)}`);
97
+ }
98
+ return;
99
+ }
100
+ try {
101
+ socket.send(JSON.stringify(payload));
102
+ }
103
+ catch (error) {
104
+ console.log(`[android] send failed serial=${serial} type=${String(payload.type)} ${error instanceof Error ? error.message : 'unknown'}`);
105
+ }
106
+ };
107
+ const shutdown = (reason) => {
108
+ if (closed)
109
+ return;
110
+ closed = true;
111
+ clearInterval(flowTimer);
112
+ if (statsTimer)
113
+ clearInterval(statsTimer);
114
+ if (DEBUG_ANDROID)
115
+ console.log(`[android] session end serial=${serial} sent=${sent} acked=${acked} reason=${reason ?? 'ok'}`);
116
+ if (reason)
117
+ send({ type: 'error', message: reason });
118
+ try {
119
+ session?.stop();
120
+ }
121
+ catch { /* ignore */ }
122
+ session = null;
123
+ try {
124
+ socket.close(1000, reason ? 'error' : 'closed');
125
+ }
126
+ catch { /* ignore */ }
127
+ };
128
+ socket.on('close', () => { closed = true; clearInterval(flowTimer); if (statsTimer)
129
+ clearInterval(statsTimer); void session?.stop(); session = null; });
130
+ socket.on('error', () => { closed = true; clearInterval(flowTimer); if (statsTimer)
131
+ clearInterval(statsTimer); void session?.stop(); session = null; });
132
+ session = createScrcpySession(serial, {
133
+ onHeader: header => { send({ type: 'header', ...header }); },
134
+ onFrame: frame => {
135
+ if (closed)
136
+ return;
137
+ // 不丢任何帧:客户端慢时暂停视频 socket,用反压换低延迟。
138
+ inflight++;
139
+ if (DEBUG_ANDROID && (sent < 12 || frame.keyFrame))
140
+ console.log(`[android] send frame #${sent} serial=${serial} config=${frame.config} key=${frame.keyFrame} bytes=${frame.data.length}`);
141
+ sent++;
142
+ lastFrameAt = Date.now();
143
+ send({
144
+ type: 'frame', seq: ++seq, config: frame.config, key: frame.keyFrame,
145
+ pts: frame.pts.toString(), data: frame.data.toString('base64'),
146
+ });
147
+ if (!videoPaused && inflight >= FRAME_WINDOW) {
148
+ videoPaused = true;
149
+ session?.pauseVideo();
150
+ }
151
+ },
152
+ onError: error => shutdown(error.message || 'SCRCPY_ERROR'),
153
+ onClose: () => { if (!closed) {
154
+ send({ type: 'closed' });
155
+ closed = true;
156
+ try {
157
+ socket.close(1000, 'closed');
158
+ }
159
+ catch { /* ignore */ }
160
+ } },
161
+ }, quality);
162
+ void session.start().catch(error => shutdown(error instanceof Error ? error.message : 'SCRCPY_START_FAILED'));
163
+ socket.on('message', raw => {
164
+ if (closed || !session)
165
+ return;
166
+ let message;
167
+ try {
168
+ message = JSON.parse(typeof raw === 'string' ? raw : raw.toString('utf8'));
169
+ }
170
+ catch {
171
+ return;
172
+ }
173
+ switch (message.type) {
174
+ case 'control': {
175
+ if (typeof message.data !== 'string' || message.data.length > 64 * 1024)
176
+ return;
177
+ const bytes = Buffer.from(message.data, 'base64');
178
+ const ok = session.sendControl(bytes);
179
+ if (DEBUG_ANDROID && controlCount++ < 8) {
180
+ const detail = bytes[0] === 2 && bytes.length >= 32
181
+ ? ` touch action=${bytes[1]} x=${bytes.readUInt32BE(10)} y=${bytes.readUInt32BE(14)} size=${bytes.readUInt16BE(18)}x${bytes.readUInt16BE(20)}`
182
+ : ` type=${bytes[0]}`;
183
+ console.log(`[android] control #${controlCount} serial=${serial} bytes=${bytes.length} written=${ok}${detail}`);
184
+ }
185
+ break;
186
+ }
187
+ case 'ack': {
188
+ if (typeof message.seq === 'number') {
189
+ inflight = Math.max(0, inflight - 1);
190
+ acked++;
191
+ lastAckAt = Date.now();
192
+ if (videoPaused && inflight < FRAME_WINDOW) {
193
+ videoPaused = false;
194
+ session?.resumeVideo();
195
+ }
196
+ }
197
+ break;
198
+ }
199
+ case 'ping':
200
+ send({ type: 'pong' });
201
+ break;
202
+ default: break;
203
+ }
204
+ });
205
+ }