termdock 1.4.248 → 1.4.250

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-DUXUkp6M.js} +35 -35
  2. package/dist/client/assets/App-DUXUkp6M.js.br +0 -0
  3. package/dist/client/assets/App-DUXUkp6M.js.gz +0 -0
  4. package/dist/client/assets/{ChangeWalkthroughPanel-vDiLbIdv.js → ChangeWalkthroughPanel-DuftIP_n.js} +2 -2
  5. package/dist/client/assets/ChangeWalkthroughPanel-DuftIP_n.js.br +0 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-DuftIP_n.js.gz +0 -0
  7. package/dist/client/assets/{DagPlayground-BvY_KblL.js → DagPlayground-DUStLbuX.js} +1 -1
  8. package/dist/client/assets/DagPlayground-DUStLbuX.js.br +0 -0
  9. package/dist/client/assets/DagPlayground-DUStLbuX.js.gz +0 -0
  10. package/dist/client/assets/{DiffLab-Dra7cEKx.js → DiffLab-r4yGPw3X.js} +1 -1
  11. package/dist/client/assets/DiffLab-r4yGPw3X.js.br +0 -0
  12. package/dist/client/assets/DiffLab-r4yGPw3X.js.gz +0 -0
  13. package/dist/client/assets/{DiffReview-v9_1IS2n.js → DiffReview-Bv30J0_c.js} +1 -1
  14. package/dist/client/assets/DiffReview-Bv30J0_c.js.br +0 -0
  15. package/dist/client/assets/DiffReview-Bv30J0_c.js.gz +0 -0
  16. package/dist/client/assets/{DiffReviewLab-BF8Dada6.js → DiffReviewLab-DNKv08iv.js} +1 -1
  17. package/dist/client/assets/DiffReviewLab-DNKv08iv.js.br +0 -0
  18. package/dist/client/assets/DiffReviewLab-DNKv08iv.js.gz +0 -0
  19. package/dist/client/assets/{DiffViewer-C2s4BXp0.js → DiffViewer-C2CNzWOk.js} +1 -1
  20. package/dist/client/assets/DiffViewer-C2CNzWOk.js.br +0 -0
  21. package/dist/client/assets/DiffViewer-C2CNzWOk.js.gz +0 -0
  22. package/dist/client/assets/{ModelPreview-D9pzW_YD.js → ModelPreview-CKrfGqLc.js} +1 -1
  23. package/dist/client/assets/ModelPreview-CKrfGqLc.js.br +0 -0
  24. package/dist/client/assets/ModelPreview-CKrfGqLc.js.gz +0 -0
  25. package/dist/client/assets/RightSidebar-CghCWdM7.js +1678 -0
  26. package/dist/client/assets/RightSidebar-CghCWdM7.js.br +0 -0
  27. package/dist/client/assets/RightSidebar-CghCWdM7.js.gz +0 -0
  28. package/dist/client/assets/{abnfDiagram-VCTEODGH-DVOxqZDY.js → abnfDiagram-VCTEODGH-DL2nfDNT.js} +1 -1
  29. package/dist/client/assets/abnfDiagram-VCTEODGH-DL2nfDNT.js.br +0 -0
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-DL2nfDNT.js.gz +0 -0
  31. package/dist/client/assets/{app-JK4pM9zv.js → app-8tWJbUN-.js} +11 -11
  32. package/dist/client/assets/app-8tWJbUN-.js.br +0 -0
  33. package/dist/client/assets/app-8tWJbUN-.js.gz +0 -0
  34. package/dist/client/assets/{arc-DY_WK1Ln.js → arc-V2GgKn7I.js} +1 -1
  35. package/dist/client/assets/arc-V2GgKn7I.js.br +0 -0
  36. package/dist/client/assets/arc-V2GgKn7I.js.gz +0 -0
  37. package/dist/client/assets/{architectureDiagram-5GKGNRK7-oyhVtQ5E.js → architectureDiagram-5GKGNRK7-Fg90zpOW.js} +1 -1
  38. package/dist/client/assets/architectureDiagram-5GKGNRK7-Fg90zpOW.js.br +0 -0
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-Fg90zpOW.js.gz +0 -0
  40. package/dist/client/assets/{blockDiagram-I7D4REHJ-DTMir8MU.js → blockDiagram-I7D4REHJ-BH0MOsEc.js} +1 -1
  41. package/dist/client/assets/blockDiagram-I7D4REHJ-BH0MOsEc.js.br +0 -0
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-BH0MOsEc.js.gz +0 -0
  43. package/dist/client/assets/{c4Diagram-7LVT6UL2-DpDHYMUW.js → c4Diagram-7LVT6UL2-BCTc14IT.js} +1 -1
  44. package/dist/client/assets/c4Diagram-7LVT6UL2-BCTc14IT.js.br +0 -0
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-BCTc14IT.js.gz +0 -0
  46. package/dist/client/assets/channel-ByAQak-i.js +1 -0
  47. package/dist/client/assets/{chunk-2Q5K7J3B-CZw9qV1g.js → chunk-2Q5K7J3B-DcJlbXqa.js} +1 -1
  48. package/dist/client/assets/{chunk-5VM5RSS4-7oLZ2vUy.js → chunk-5VM5RSS4-YadWuzn7.js} +1 -1
  49. package/dist/client/assets/{chunk-F27PBJKO-8wDqj2MV.js → chunk-F27PBJKO-BTaBHq7k.js} +1 -1
  50. package/dist/client/assets/chunk-F27PBJKO-BTaBHq7k.js.br +0 -0
  51. package/dist/client/assets/chunk-F27PBJKO-BTaBHq7k.js.gz +0 -0
  52. package/dist/client/assets/{chunk-IMKFNOWR-BnRNosQc.js → chunk-IMKFNOWR-BOqgrA6m.js} +1 -1
  53. package/dist/client/assets/chunk-IMKFNOWR-BOqgrA6m.js.br +0 -0
  54. package/dist/client/assets/chunk-IMKFNOWR-BOqgrA6m.js.gz +0 -0
  55. package/dist/client/assets/{chunk-JWPE2WC7-DnbB0Lhi.js → chunk-JWPE2WC7-CsX0TZuJ.js} +1 -1
  56. package/dist/client/assets/{chunk-POPQ4Y6H-TXREax5x.js → chunk-POPQ4Y6H-Bl2l1Ema.js} +1 -1
  57. package/dist/client/assets/{chunk-SVP7TREG-qPD0Q74z.js → chunk-SVP7TREG-DuL_oy8M.js} +1 -1
  58. package/dist/client/assets/chunk-SVP7TREG-DuL_oy8M.js.br +0 -0
  59. package/dist/client/assets/chunk-SVP7TREG-DuL_oy8M.js.gz +0 -0
  60. package/dist/client/assets/{chunk-TICWLB2K-yzLwt_MH.js → chunk-TICWLB2K-BpNqLBuo.js} +1 -1
  61. package/dist/client/assets/chunk-TICWLB2K-BpNqLBuo.js.br +0 -0
  62. package/dist/client/assets/chunk-TICWLB2K-BpNqLBuo.js.gz +0 -0
  63. package/dist/client/assets/{chunk-XXDRQBXY-Dksq2NzZ.js → chunk-XXDRQBXY-DvOZIuyY.js} +1 -1
  64. package/dist/client/assets/classDiagram-ZZMXUADV-U0ztcNG4.js +1 -0
  65. package/dist/client/assets/classDiagram-v2-VYDZK3BY-U0ztcNG4.js +1 -0
  66. package/dist/client/assets/{cose-bilkent-JH36ORCC-BZyAGO6R.js → cose-bilkent-JH36ORCC-ChxuLhhv.js} +1 -1
  67. package/dist/client/assets/cose-bilkent-JH36ORCC-ChxuLhhv.js.br +0 -0
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-ChxuLhhv.js.gz +0 -0
  69. package/dist/client/assets/{cynefin-OW5HDTMX-CyrkJWSU.js → cynefin-OW5HDTMX-C071INwn.js} +1 -1
  70. package/dist/client/assets/cynefin-OW5HDTMX-C071INwn.js.br +0 -0
  71. package/dist/client/assets/cynefin-OW5HDTMX-C071INwn.js.gz +0 -0
  72. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BrqS8ZGX.js → cynefinDiagram-5FMLGOSQ-MY_50dMe.js} +1 -1
  73. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-MY_50dMe.js.br +0 -0
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-MY_50dMe.js.gz +0 -0
  75. package/dist/client/assets/{dagre-GXQ25YYZ-ysmdkSNr.js → dagre-GXQ25YYZ-DELhrgXz.js} +1 -1
  76. package/dist/client/assets/dagre-GXQ25YYZ-DELhrgXz.js.br +0 -0
  77. package/dist/client/assets/dagre-GXQ25YYZ-DELhrgXz.js.gz +0 -0
  78. package/dist/client/assets/{diagram-S7CK7UJ4-CWSuzX1d.js → diagram-S7CK7UJ4-B-Be7FNM.js} +1 -1
  79. package/dist/client/assets/diagram-S7CK7UJ4-B-Be7FNM.js.br +0 -0
  80. package/dist/client/assets/diagram-S7CK7UJ4-B-Be7FNM.js.gz +0 -0
  81. package/dist/client/assets/{diagram-UQ7AKVKN-BMxc5NDd.js → diagram-UQ7AKVKN-BljlErc_.js} +1 -1
  82. package/dist/client/assets/diagram-UQ7AKVKN-BljlErc_.js.br +0 -0
  83. package/dist/client/assets/diagram-UQ7AKVKN-BljlErc_.js.gz +0 -0
  84. package/dist/client/assets/{diagram-VSXAHHWV-Cz5KpnGX.js → diagram-VSXAHHWV-CedNK-uM.js} +1 -1
  85. package/dist/client/assets/diagram-VSXAHHWV-CedNK-uM.js.br +0 -0
  86. package/dist/client/assets/diagram-VSXAHHWV-CedNK-uM.js.gz +0 -0
  87. package/dist/client/assets/{diagram-VX7I27RA-CNk1h20b.js → diagram-VX7I27RA-BpF063CL.js} +1 -1
  88. package/dist/client/assets/diagram-VX7I27RA-BpF063CL.js.br +0 -0
  89. package/dist/client/assets/diagram-VX7I27RA-BpF063CL.js.gz +0 -0
  90. package/dist/client/assets/{diagram-Z3DM3KII-C4lcgl6z.js → diagram-Z3DM3KII-DmykUds5.js} +1 -1
  91. package/dist/client/assets/diagram-Z3DM3KII-DmykUds5.js.br +0 -0
  92. package/dist/client/assets/diagram-Z3DM3KII-DmykUds5.js.gz +0 -0
  93. package/dist/client/assets/{ebnfDiagram-PWID7BFC-Cwo356mF.js → ebnfDiagram-PWID7BFC-DZkhpsQ7.js} +1 -1
  94. package/dist/client/assets/ebnfDiagram-PWID7BFC-DZkhpsQ7.js.br +0 -0
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-DZkhpsQ7.js.gz +0 -0
  96. package/dist/client/assets/{erDiagram-RLTQ6QDP-B4DEocxu.js → erDiagram-RLTQ6QDP-7EOAP4ib.js} +1 -1
  97. package/dist/client/assets/erDiagram-RLTQ6QDP-7EOAP4ib.js.br +0 -0
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-7EOAP4ib.js.gz +0 -0
  99. package/dist/client/assets/{flowDiagram-HODETNUW-CWP3R7jz.js → flowDiagram-HODETNUW-Di-NXz6s.js} +1 -1
  100. package/dist/client/assets/flowDiagram-HODETNUW-Di-NXz6s.js.br +0 -0
  101. package/dist/client/assets/flowDiagram-HODETNUW-Di-NXz6s.js.gz +0 -0
  102. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-DBdMWa6L.js → ganttDiagram-EL5Y4UJY-BXGP48XI.js} +1 -1
  103. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BXGP48XI.js.br +0 -0
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BXGP48XI.js.gz +0 -0
  105. package/dist/client/assets/{gestureArbiter-FNHriuhM.js → gestureArbiter-DfjioB30.js} +1 -1
  106. package/dist/client/assets/gestureArbiter-DfjioB30.js.br +0 -0
  107. package/dist/client/assets/gestureArbiter-DfjioB30.js.gz +0 -0
  108. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-B6qG6eRT.js → gitGraphDiagram-WWUBYQGX-DMWvrtdU.js} +1 -1
  109. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DMWvrtdU.js.br +0 -0
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DMWvrtdU.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-B6A9Pji6.js} +1 -1
  115. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-Cz8OXUK9.js → ishikawaDiagram-5VMMS53U-BjBHrYpy.js} +1 -1
  116. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BjBHrYpy.js.br +0 -0
  117. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BjBHrYpy.js.gz +0 -0
  118. package/dist/client/assets/{journeyDiagram-3NMN7TZE-CD4v2_eb.js → journeyDiagram-3NMN7TZE-B-OK36-I.js} +1 -1
  119. package/dist/client/assets/journeyDiagram-3NMN7TZE-B-OK36-I.js.br +0 -0
  120. package/dist/client/assets/journeyDiagram-3NMN7TZE-B-OK36-I.js.gz +0 -0
  121. package/dist/client/assets/{kanban-definition-UXKFOSKX-DwLWqTSY.js → kanban-definition-UXKFOSKX-BkR_GpZf.js} +1 -1
  122. package/dist/client/assets/kanban-definition-UXKFOSKX-BkR_GpZf.js.br +0 -0
  123. package/dist/client/assets/kanban-definition-UXKFOSKX-BkR_GpZf.js.gz +0 -0
  124. package/dist/client/assets/{layout-C1EhlDX0.js → layout-B1dVqanE.js} +1 -1
  125. package/dist/client/assets/layout-B1dVqanE.js.br +0 -0
  126. package/dist/client/assets/layout-B1dVqanE.js.gz +0 -0
  127. package/dist/client/assets/{linear-DQLRPRnN.js → linear-DbtXwCMs.js} +1 -1
  128. package/dist/client/assets/linear-DbtXwCMs.js.br +0 -0
  129. package/dist/client/assets/linear-DbtXwCMs.js.gz +0 -0
  130. package/dist/client/assets/{mermaid.core-BzEGo4OD.js → mermaid.core-DXBQ8sgL.js} +5 -5
  131. package/dist/client/assets/mermaid.core-DXBQ8sgL.js.br +0 -0
  132. package/dist/client/assets/mermaid.core-DXBQ8sgL.js.gz +0 -0
  133. package/dist/client/assets/{mindmap-definition-YA3MSWOX-Dct8OiDY.js → mindmap-definition-YA3MSWOX-aErXsDjT.js} +1 -1
  134. package/dist/client/assets/mindmap-definition-YA3MSWOX-aErXsDjT.js.br +0 -0
  135. package/dist/client/assets/mindmap-definition-YA3MSWOX-aErXsDjT.js.gz +0 -0
  136. package/dist/client/assets/{passwordBootstrap-C7Gy7ATG.js → passwordBootstrap-LVbx2WyB.js} +1 -1
  137. package/dist/client/assets/passwordBootstrap-LVbx2WyB.js.br +0 -0
  138. package/dist/client/assets/passwordBootstrap-LVbx2WyB.js.gz +0 -0
  139. package/dist/client/assets/{pegDiagram-XKGWAZYB-fIABDYj-.js → pegDiagram-XKGWAZYB-B7UP1Iyq.js} +1 -1
  140. package/dist/client/assets/pegDiagram-XKGWAZYB-B7UP1Iyq.js.br +0 -0
  141. package/dist/client/assets/pegDiagram-XKGWAZYB-B7UP1Iyq.js.gz +0 -0
  142. package/dist/client/assets/{pieDiagram-E7YTZNPT-D2EuLDIj.js → pieDiagram-E7YTZNPT-DDVenGYn.js} +1 -1
  143. package/dist/client/assets/pieDiagram-E7YTZNPT-DDVenGYn.js.br +0 -0
  144. package/dist/client/assets/pieDiagram-E7YTZNPT-DDVenGYn.js.gz +0 -0
  145. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BjF42rwO.js → quadrantDiagram-AXDQQJYC-BfVFS4dx.js} +1 -1
  146. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BfVFS4dx.js.br +0 -0
  147. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BfVFS4dx.js.gz +0 -0
  148. package/dist/client/assets/{railroadDiagram-O6MQD6OU-DjfSTDvw.js → railroadDiagram-O6MQD6OU-DMmkHIlO.js} +1 -1
  149. package/dist/client/assets/railroadDiagram-O6MQD6OU-DMmkHIlO.js.br +0 -0
  150. package/dist/client/assets/railroadDiagram-O6MQD6OU-DMmkHIlO.js.gz +0 -0
  151. package/dist/client/assets/{referenceLongPress-Cz9no5pI.js → referenceLongPress-CxWG0c2F.js} +1 -1
  152. package/dist/client/assets/referenceLongPress-CxWG0c2F.js.br +0 -0
  153. package/dist/client/assets/referenceLongPress-CxWG0c2F.js.gz +0 -0
  154. package/dist/client/assets/{requirementDiagram-BXWQKSXE-BewZ62Ou.js → requirementDiagram-BXWQKSXE-BAK1hsXp.js} +1 -1
  155. package/dist/client/assets/requirementDiagram-BXWQKSXE-BAK1hsXp.js.br +0 -0
  156. package/dist/client/assets/requirementDiagram-BXWQKSXE-BAK1hsXp.js.gz +0 -0
  157. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-BhlchAkm.js → sankeyDiagram-P5KCCOFB-BEvnkr6_.js} +1 -1
  158. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BEvnkr6_.js.br +0 -0
  159. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BEvnkr6_.js.gz +0 -0
  160. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CFzratAc.js → sequenceDiagram-WJ2MYXX4-Ie1mihcT.js} +1 -1
  161. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CFzratAc.js.br → sequenceDiagram-WJ2MYXX4-Ie1mihcT.js.br} +0 -0
  162. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Ie1mihcT.js.gz +0 -0
  163. package/dist/client/assets/{sizeCapture-INFHLROL-DeLB3S5x.js → sizeCapture-INFHLROL-Dxfdgg7e.js} +1 -1
  164. package/dist/client/assets/sizeCapture-INFHLROL-Dxfdgg7e.js.br +0 -0
  165. package/dist/client/assets/sizeCapture-INFHLROL-Dxfdgg7e.js.gz +0 -0
  166. package/dist/client/assets/{sparkles-BHsBDSo5.js → sparkles-EjjUH4DP.js} +1 -1
  167. package/dist/client/assets/{stateDiagram-D77RDMKH-smIkttxr.js → stateDiagram-D77RDMKH-D9nsU2MY.js} +1 -1
  168. package/dist/client/assets/stateDiagram-D77RDMKH-D9nsU2MY.js.br +0 -0
  169. package/dist/client/assets/stateDiagram-D77RDMKH-D9nsU2MY.js.gz +0 -0
  170. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-EDm7-sTV.js +1 -0
  171. package/dist/client/assets/{swimlanes-42K2YHIH-CVwGuPcK.js → swimlanes-42K2YHIH-C0-l-AhO.js} +1 -1
  172. package/dist/client/assets/swimlanes-42K2YHIH-C0-l-AhO.js.br +0 -0
  173. package/dist/client/assets/swimlanes-42K2YHIH-C0-l-AhO.js.gz +0 -0
  174. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-B2H2z_dX.js +8 -0
  175. package/dist/client/assets/{timeline-definition-24CTP7MA-Dsn_Rxbd.js → timeline-definition-24CTP7MA-DwOPp-ah.js} +1 -1
  176. package/dist/client/assets/timeline-definition-24CTP7MA-DwOPp-ah.js.br +0 -0
  177. package/dist/client/assets/timeline-definition-24CTP7MA-DwOPp-ah.js.gz +0 -0
  178. package/dist/client/assets/{triangle-alert-DexJX8jR.js → triangle-alert-BXlTfgBy.js} +1 -1
  179. package/dist/client/assets/{vennDiagram-4TSXK5OY-Dtdth9UN.js → vennDiagram-4TSXK5OY-BYOmQzP3.js} +1 -1
  180. package/dist/client/assets/vennDiagram-4TSXK5OY-BYOmQzP3.js.br +0 -0
  181. package/dist/client/assets/vennDiagram-4TSXK5OY-BYOmQzP3.js.gz +0 -0
  182. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-82oGoyOd.js → wardleyDiagram-VM6X3IG4-B2yYZbOq.js} +1 -1
  183. package/dist/client/assets/wardleyDiagram-VM6X3IG4-B2yYZbOq.js.br +0 -0
  184. package/dist/client/assets/wardleyDiagram-VM6X3IG4-B2yYZbOq.js.gz +0 -0
  185. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Ygu0NbUo.js → xychartDiagram-S5SC5T6Z-DdqrbuEU.js} +1 -1
  186. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DdqrbuEU.js.br +0 -0
  187. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Ygu0NbUo.js.gz → xychartDiagram-S5SC5T6Z-DdqrbuEU.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 +421 -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/architectureDiagram-5GKGNRK7-oyhVtQ5E.js.gz +0 -0
  231. package/dist/client/assets/blockDiagram-I7D4REHJ-DTMir8MU.js.br +0 -0
  232. package/dist/client/assets/blockDiagram-I7D4REHJ-DTMir8MU.js.gz +0 -0
  233. package/dist/client/assets/c4Diagram-7LVT6UL2-DpDHYMUW.js.br +0 -0
  234. package/dist/client/assets/c4Diagram-7LVT6UL2-DpDHYMUW.js.gz +0 -0
  235. package/dist/client/assets/channel-cexw6K2f.js +0 -1
  236. package/dist/client/assets/chunk-F27PBJKO-8wDqj2MV.js.br +0 -0
  237. package/dist/client/assets/chunk-F27PBJKO-8wDqj2MV.js.gz +0 -0
  238. package/dist/client/assets/chunk-IMKFNOWR-BnRNosQc.js.br +0 -0
  239. package/dist/client/assets/chunk-IMKFNOWR-BnRNosQc.js.gz +0 -0
  240. package/dist/client/assets/chunk-SVP7TREG-qPD0Q74z.js.br +0 -0
  241. package/dist/client/assets/chunk-SVP7TREG-qPD0Q74z.js.gz +0 -0
  242. package/dist/client/assets/chunk-TICWLB2K-yzLwt_MH.js.br +0 -0
  243. package/dist/client/assets/chunk-TICWLB2K-yzLwt_MH.js.gz +0 -0
  244. package/dist/client/assets/classDiagram-ZZMXUADV-CpjKakYG.js +0 -1
  245. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CpjKakYG.js +0 -1
  246. package/dist/client/assets/cose-bilkent-JH36ORCC-BZyAGO6R.js.br +0 -0
  247. package/dist/client/assets/cose-bilkent-JH36ORCC-BZyAGO6R.js.gz +0 -0
  248. package/dist/client/assets/cynefin-OW5HDTMX-CyrkJWSU.js.br +0 -0
  249. package/dist/client/assets/cynefin-OW5HDTMX-CyrkJWSU.js.gz +0 -0
  250. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BrqS8ZGX.js.br +0 -0
  251. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BrqS8ZGX.js.gz +0 -0
  252. package/dist/client/assets/dagre-GXQ25YYZ-ysmdkSNr.js.br +0 -0
  253. package/dist/client/assets/dagre-GXQ25YYZ-ysmdkSNr.js.gz +0 -0
  254. package/dist/client/assets/diagram-S7CK7UJ4-CWSuzX1d.js.br +0 -0
  255. package/dist/client/assets/diagram-S7CK7UJ4-CWSuzX1d.js.gz +0 -0
  256. package/dist/client/assets/diagram-UQ7AKVKN-BMxc5NDd.js.br +0 -0
  257. package/dist/client/assets/diagram-UQ7AKVKN-BMxc5NDd.js.gz +0 -0
  258. package/dist/client/assets/diagram-VSXAHHWV-Cz5KpnGX.js.br +0 -0
  259. package/dist/client/assets/diagram-VSXAHHWV-Cz5KpnGX.js.gz +0 -0
  260. package/dist/client/assets/diagram-VX7I27RA-CNk1h20b.js.br +0 -0
  261. package/dist/client/assets/diagram-VX7I27RA-CNk1h20b.js.gz +0 -0
  262. package/dist/client/assets/diagram-Z3DM3KII-C4lcgl6z.js.br +0 -0
  263. package/dist/client/assets/diagram-Z3DM3KII-C4lcgl6z.js.gz +0 -0
  264. package/dist/client/assets/ebnfDiagram-PWID7BFC-Cwo356mF.js.br +0 -0
  265. package/dist/client/assets/ebnfDiagram-PWID7BFC-Cwo356mF.js.gz +0 -0
  266. package/dist/client/assets/erDiagram-RLTQ6QDP-B4DEocxu.js.br +0 -0
  267. package/dist/client/assets/erDiagram-RLTQ6QDP-B4DEocxu.js.gz +0 -0
  268. package/dist/client/assets/flowDiagram-HODETNUW-CWP3R7jz.js.br +0 -0
  269. package/dist/client/assets/flowDiagram-HODETNUW-CWP3R7jz.js.gz +0 -0
  270. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DBdMWa6L.js.br +0 -0
  271. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DBdMWa6L.js.gz +0 -0
  272. package/dist/client/assets/gestureArbiter-FNHriuhM.js.br +0 -0
  273. package/dist/client/assets/gestureArbiter-FNHriuhM.js.gz +0 -0
  274. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-B6qG6eRT.js.br +0 -0
  275. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-B6qG6eRT.js.gz +0 -0
  276. package/dist/client/assets/index-fjWWhVQp.css.br +0 -0
  277. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cz8OXUK9.js.br +0 -0
  278. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cz8OXUK9.js.gz +0 -0
  279. package/dist/client/assets/journeyDiagram-3NMN7TZE-CD4v2_eb.js.br +0 -0
  280. package/dist/client/assets/journeyDiagram-3NMN7TZE-CD4v2_eb.js.gz +0 -0
  281. package/dist/client/assets/kanban-definition-UXKFOSKX-DwLWqTSY.js.br +0 -0
  282. package/dist/client/assets/kanban-definition-UXKFOSKX-DwLWqTSY.js.gz +0 -0
  283. package/dist/client/assets/layout-C1EhlDX0.js.br +0 -0
  284. package/dist/client/assets/layout-C1EhlDX0.js.gz +0 -0
  285. package/dist/client/assets/linear-DQLRPRnN.js.br +0 -0
  286. package/dist/client/assets/linear-DQLRPRnN.js.gz +0 -0
  287. package/dist/client/assets/mermaid.core-BzEGo4OD.js.br +0 -0
  288. package/dist/client/assets/mermaid.core-BzEGo4OD.js.gz +0 -0
  289. package/dist/client/assets/mindmap-definition-YA3MSWOX-Dct8OiDY.js.br +0 -0
  290. package/dist/client/assets/mindmap-definition-YA3MSWOX-Dct8OiDY.js.gz +0 -0
  291. package/dist/client/assets/passwordBootstrap-C7Gy7ATG.js.br +0 -0
  292. package/dist/client/assets/passwordBootstrap-C7Gy7ATG.js.gz +0 -0
  293. package/dist/client/assets/pegDiagram-XKGWAZYB-fIABDYj-.js.br +0 -0
  294. package/dist/client/assets/pegDiagram-XKGWAZYB-fIABDYj-.js.gz +0 -0
  295. package/dist/client/assets/pieDiagram-E7YTZNPT-D2EuLDIj.js.br +0 -0
  296. package/dist/client/assets/pieDiagram-E7YTZNPT-D2EuLDIj.js.gz +0 -0
  297. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BjF42rwO.js.br +0 -0
  298. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BjF42rwO.js.gz +0 -0
  299. package/dist/client/assets/railroadDiagram-O6MQD6OU-DjfSTDvw.js.br +0 -0
  300. package/dist/client/assets/railroadDiagram-O6MQD6OU-DjfSTDvw.js.gz +0 -0
  301. package/dist/client/assets/referenceLongPress-Cz9no5pI.js.br +0 -0
  302. package/dist/client/assets/referenceLongPress-Cz9no5pI.js.gz +0 -0
  303. package/dist/client/assets/requirementDiagram-BXWQKSXE-BewZ62Ou.js.br +0 -0
  304. package/dist/client/assets/requirementDiagram-BXWQKSXE-BewZ62Ou.js.gz +0 -0
  305. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BhlchAkm.js.br +0 -0
  306. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BhlchAkm.js.gz +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.br +0 -0
@@ -0,0 +1,421 @@
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
+ /** scrcpy 4.0 起:视频头只有 4 字节 codec id,宽高改为独立的 session 包;frame flags 也下移一位。 */
142
+ protocol = 'legacy';
143
+ videoCodec = null;
144
+ port = 0;
145
+ stopped = false;
146
+ started = false;
147
+ deviceName = '';
148
+ resolveHeader = null;
149
+ logLines = [];
150
+ constructor(serial, events, options = {}) {
151
+ this.serial = serial;
152
+ this.events = events;
153
+ this.options = options;
154
+ }
155
+ get running() { return this.started && !this.stopped; }
156
+ log(line) {
157
+ const text = line.trim();
158
+ if (!text)
159
+ return;
160
+ this.logLines.push(text);
161
+ if (this.logLines.length > 40)
162
+ this.logLines.shift();
163
+ }
164
+ fail(error) {
165
+ if (this.stopped)
166
+ return;
167
+ this.events.onError(error);
168
+ void this.stop();
169
+ }
170
+ async start() {
171
+ if (this.started)
172
+ return;
173
+ this.started = true;
174
+ try {
175
+ const { serverJar, version } = await resolveScrcpyBinaries();
176
+ this.protocol = Number.parseInt(version.split('.')[0] ?? '0', 10) >= 4 ? 'session' : 'legacy';
177
+ const remoteJar = remoteJarPath(version);
178
+ await runAdbCapture(['-s', this.serial, 'push', serverJar, remoteJar], 60_000);
179
+ const server = net.createServer();
180
+ this.server = server;
181
+ server.on('connection', socket => this.acceptSocket(socket));
182
+ await new Promise((resolve, reject) => {
183
+ server.once('error', reject);
184
+ server.listen(0, '127.0.0.1', () => { this.port = server.address().port; resolve(); });
185
+ });
186
+ await runAdbCapture(['-s', this.serial, 'reverse', `localabstract:scrcpy_${scidHex(this.scid)}`, `tcp:${this.port}`], 15_000);
187
+ const args = [
188
+ '-s', this.serial, 'shell',
189
+ `CLASSPATH=${remoteJar}`,
190
+ 'app_process', '/',
191
+ 'com.genymobile.scrcpy.Server', version,
192
+ `scid=${this.scid.toString(16)}`,
193
+ 'log_level=info',
194
+ 'audio=false',
195
+ 'control=true',
196
+ 'tunnel_forward=false',
197
+ 'power_on=true',
198
+ 'stay_awake=true',
199
+ 'clipboard_autosync=false',
200
+ `max_size=${this.options.maxSize ?? 1600}`,
201
+ `video_bit_rate=${this.options.videoBitRate ?? 8_000_000}`,
202
+ ];
203
+ if (this.options.maxFps && this.options.maxFps > 0)
204
+ args.push(`max_fps=${this.options.maxFps}`);
205
+ const child = spawn(adbBinary(), args, { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, PATH: adbSearchPath() } });
206
+ this.child = child;
207
+ child.stdout?.on('data', chunk => this.log(String(chunk).replace(/^\[server\]\s*/, '')));
208
+ child.stderr?.on('data', chunk => this.log(String(chunk)));
209
+ child.on('error', error => this.fail(error instanceof Error ? error : new Error('SCRCPY_SPAWN_FAILED')));
210
+ child.on('close', () => { if (!this.stopped)
211
+ this.fail(new Error(this.logLines.at(-1) || 'SCRCPY_SERVER_EXITED')); });
212
+ await this.waitForHeader();
213
+ }
214
+ catch (error) {
215
+ const normalized = error instanceof Error ? error : new Error('SCRCPY_START_FAILED');
216
+ if (!this.stopped)
217
+ this.fail(normalized);
218
+ throw normalized;
219
+ }
220
+ }
221
+ waitForHeader() {
222
+ return new Promise((resolve, reject) => {
223
+ const timer = setTimeout(() => { this.resolveHeader = null; reject(new Error('SCRCPY_CONNECT_TIMEOUT')); }, CONNECT_TIMEOUT_MS);
224
+ this.resolveHeader = () => { clearTimeout(timer); this.resolveHeader = null; resolve(); };
225
+ });
226
+ }
227
+ acceptSocket(socket) {
228
+ socket.setNoDelay(true);
229
+ if (!this.videoSocket) {
230
+ this.videoSocket = socket;
231
+ socket.on('data', chunk => this.readVideo(chunk));
232
+ socket.on('error', error => this.fail(error instanceof Error ? error : new Error('VIDEO_SOCKET_ERROR')));
233
+ socket.on('close', () => { if (!this.stopped)
234
+ this.fail(new Error(this.logLines.at(-1) || 'VIDEO_SOCKET_CLOSED')); });
235
+ return;
236
+ }
237
+ if (!this.controlSocket) {
238
+ this.controlSocket = socket;
239
+ // 控制通道会回传剪贴板/ACK 等设备消息;v1 只消费,避免读缓冲堆积。
240
+ socket.on('data', chunk => this.readControl(chunk));
241
+ socket.on('error', () => { });
242
+ return;
243
+ }
244
+ socket.destroy();
245
+ }
246
+ readVideo(chunk) {
247
+ if (this.stopped)
248
+ return;
249
+ this.videoBuffer = Buffer.concat([this.videoBuffer, chunk]);
250
+ if (this.videoStage === 'meta') {
251
+ if (this.videoBuffer.length < DEVICE_NAME_LENGTH)
252
+ return;
253
+ this.deviceName = this.videoBuffer.subarray(0, DEVICE_NAME_LENGTH).toString('utf8').replace(/\0+$/, '');
254
+ this.videoBuffer = this.videoBuffer.subarray(DEVICE_NAME_LENGTH);
255
+ this.videoStage = 'codec';
256
+ }
257
+ if (this.videoStage === 'codec') {
258
+ if (this.protocol === 'session') {
259
+ if (this.videoBuffer.length < 4)
260
+ return;
261
+ this.videoCodec = CODEC_IDS[this.videoBuffer.readUInt32BE(0)] ?? null;
262
+ this.videoBuffer = this.videoBuffer.subarray(4);
263
+ if (!this.videoCodec) {
264
+ this.fail(new Error('SCRCPY_UNSUPPORTED_CODEC'));
265
+ return;
266
+ }
267
+ // scrcpy 4.x:宽高在随后的 session 包里。
268
+ this.videoStage = 'session';
269
+ }
270
+ else {
271
+ if (this.videoBuffer.length < VIDEO_HEADER_LENGTH)
272
+ return;
273
+ const codec = CODEC_IDS[this.videoBuffer.readUInt32BE(0)];
274
+ const width = this.videoBuffer.readUInt32BE(4);
275
+ const height = this.videoBuffer.readUInt32BE(8);
276
+ this.videoBuffer = this.videoBuffer.subarray(VIDEO_HEADER_LENGTH);
277
+ this.videoStage = 'frames';
278
+ if (!codec) {
279
+ this.fail(new Error('SCRCPY_UNSUPPORTED_CODEC'));
280
+ return;
281
+ }
282
+ this.videoCodec = codec;
283
+ this.events.onHeader({ codec, width, height, deviceName: this.deviceName || this.serial });
284
+ this.resolveHeader?.();
285
+ }
286
+ }
287
+ while (this.videoStage === 'session' && this.videoBuffer.length >= FRAME_HEADER_LENGTH) {
288
+ // session 包固定 12 字节:flags(u32, bit31=1) + width(u32) + height(u32)。
289
+ if ((this.videoBuffer[0] & 0x80) === 0) {
290
+ this.videoStage = 'frames';
291
+ break;
292
+ }
293
+ const width = this.videoBuffer.readUInt32BE(4);
294
+ const height = this.videoBuffer.readUInt32BE(8);
295
+ this.videoBuffer = this.videoBuffer.subarray(FRAME_HEADER_LENGTH);
296
+ this.videoStage = 'frames';
297
+ this.events.onHeader({ codec: this.videoCodec, width, height, deviceName: this.deviceName || this.serial });
298
+ this.resolveHeader?.();
299
+ }
300
+ while (this.videoStage === 'frames' && this.videoBuffer.length >= FRAME_HEADER_LENGTH) {
301
+ if (this.protocol === 'session' && (this.videoBuffer[0] & 0x80) !== 0) {
302
+ // 运行中分辨率变化时的 session 包。
303
+ const width = this.videoBuffer.readUInt32BE(4);
304
+ const height = this.videoBuffer.readUInt32BE(8);
305
+ this.videoBuffer = this.videoBuffer.subarray(FRAME_HEADER_LENGTH);
306
+ this.events.onHeader({ codec: this.videoCodec, width, height, deviceName: this.deviceName || this.serial });
307
+ continue;
308
+ }
309
+ const ptsFlags = this.videoBuffer.readBigUInt64BE(0);
310
+ const size = this.videoBuffer.readUInt32BE(8);
311
+ if (this.videoBuffer.length < FRAME_HEADER_LENGTH + size)
312
+ break;
313
+ const payload = Buffer.from(this.videoBuffer.subarray(FRAME_HEADER_LENGTH, FRAME_HEADER_LENGTH + size));
314
+ this.videoBuffer = this.videoBuffer.subarray(FRAME_HEADER_LENGTH + size);
315
+ const session = this.protocol === 'session';
316
+ const config = session ? ((ptsFlags >> 62n) & 1n) === 1n : ((ptsFlags >> 63n) & 1n) === 1n;
317
+ const keyFrame = session ? ((ptsFlags >> 61n) & 1n) === 1n : ((ptsFlags >> 62n) & 1n) === 1n;
318
+ const ptsMask = session ? (1n << 61n) - 1n : (1n << 62n) - 1n;
319
+ this.events.onFrame({ config, keyFrame, pts: ptsFlags & ptsMask, data: payload });
320
+ }
321
+ }
322
+ readControl(chunk) {
323
+ this.controlBuffer = Buffer.concat([this.controlBuffer, chunk]);
324
+ while (this.controlBuffer.length > 0) {
325
+ const type = this.controlBuffer[0];
326
+ if (type === 0) {
327
+ if (this.controlBuffer.length < 5)
328
+ return;
329
+ const length = this.controlBuffer.readUInt32BE(1);
330
+ if (this.controlBuffer.length < 5 + length)
331
+ return;
332
+ this.controlBuffer = this.controlBuffer.subarray(5 + length);
333
+ }
334
+ else if (type === 1) {
335
+ if (this.controlBuffer.length < 9)
336
+ return;
337
+ this.controlBuffer = this.controlBuffer.subarray(9);
338
+ }
339
+ else {
340
+ this.controlBuffer = Buffer.alloc(0);
341
+ return;
342
+ }
343
+ }
344
+ }
345
+ /** 客户端消费不过来时暂停视频 socket:让内核缓冲填满,把反压传到设备端,
346
+ * 从而在不丢帧的前提下限制延迟(丢 delta 会让参考帧链断裂、画面花屏)。 */
347
+ pauseVideo() {
348
+ if (this.videoPaused)
349
+ return;
350
+ this.videoPaused = true;
351
+ try {
352
+ this.videoSocket?.pause();
353
+ }
354
+ catch { /* ignore */ }
355
+ }
356
+ resumeVideo() {
357
+ if (!this.videoPaused)
358
+ return;
359
+ this.videoPaused = false;
360
+ try {
361
+ this.videoSocket?.resume();
362
+ }
363
+ catch { /* ignore */ }
364
+ }
365
+ videoPaused = false;
366
+ sendControl(data) {
367
+ const socket = this.controlSocket;
368
+ if (!socket || socket.destroyed)
369
+ return false;
370
+ try {
371
+ socket.write(data);
372
+ return true;
373
+ }
374
+ catch {
375
+ return false;
376
+ }
377
+ }
378
+ async stop() {
379
+ if (this.stopped)
380
+ return;
381
+ this.stopped = true;
382
+ activeSessions.delete(this);
383
+ const child = this.child;
384
+ this.child = null;
385
+ try {
386
+ this.videoSocket?.destroy();
387
+ }
388
+ catch { /* ignore */ }
389
+ try {
390
+ this.controlSocket?.destroy();
391
+ }
392
+ catch { /* ignore */ }
393
+ this.videoSocket = null;
394
+ this.controlSocket = null;
395
+ try {
396
+ this.server?.close();
397
+ }
398
+ catch { /* ignore */ }
399
+ this.server = null;
400
+ if (child) {
401
+ try {
402
+ child.kill('SIGKILL');
403
+ }
404
+ catch { /* ignore */ }
405
+ }
406
+ try {
407
+ await runAdbCapture(['-s', this.serial, 'reverse', '--remove', `localabstract:scrcpy_${scidHex(this.scid)}`], 8000);
408
+ }
409
+ catch { /* reverse 可能已随进程退出被清理 */ }
410
+ this.events.onClose();
411
+ }
412
+ }
413
+ const activeSessions = new Set();
414
+ export function createScrcpySession(serial, events, options) {
415
+ const session = new ScrcpySession(serial, events, options);
416
+ activeSessions.add(session);
417
+ return session;
418
+ }
419
+ export async function stopAllScrcpySessions() {
420
+ await Promise.all([...activeSessions].map(session => session.stop()));
421
+ }
@@ -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');