@volter/browser-substrate 0.1.118 → 0.1.120

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 (139) hide show
  1. package/catalog/demand-input.json +34 -16
  2. package/catalog/published/profile.json +8 -3
  3. package/catalog/published/v1/artifacts/sha256/1d9fc30121217ec1c2161e79c3df3e27047a786b8eb33ee6a218a887d794fa88 +4141 -0
  4. package/catalog/published/v1/artifacts/sha256/{db018a9b080d9b5fbcc6e72bc47afbdae210b679902e8dd1a269b8bd24a1bcd6 → 23a82cc8afdf35d48f0f9ef66f53ff6d7a8b3a641b7ef86e9b81ce04551ee954} +71 -25
  5. package/catalog/published/v1/artifacts/sha256/23a82cc8afdf35d48f0f9ef66f53ff6d7a8b3a641b7ef86e9b81ce04551ee954.headers.json +1 -0
  6. package/catalog/published/v1/artifacts/sha256/2ac5eba365ec12839c75c03b39b3be1dd63b798852cc460b014b52238be042f7 +2 -0
  7. package/catalog/published/v1/artifacts/sha256/2ac5eba365ec12839c75c03b39b3be1dd63b798852cc460b014b52238be042f7.headers.json +1 -0
  8. package/catalog/published/v1/artifacts/sha256/301eea90d7061167d148cc37016a0dd77b58ca86d378ee27f720ef1ea62df9e8 +0 -0
  9. package/catalog/published/v1/artifacts/sha256/301eea90d7061167d148cc37016a0dd77b58ca86d378ee27f720ef1ea62df9e8.headers.json +1 -0
  10. package/catalog/published/v1/artifacts/sha256/3a0a00dfeaa348ac20f9ef09904233d32d33f644339662d4af368f8a2010f37a +0 -0
  11. package/catalog/published/v1/artifacts/sha256/3a0a00dfeaa348ac20f9ef09904233d32d33f644339662d4af368f8a2010f37a.headers.json +1 -0
  12. package/catalog/published/v1/artifacts/sha256/3fdaef09e9e365c85e002737720f8d0ab8f278c1c244a2dde6a37663cf488ad4 +1 -0
  13. package/catalog/published/v1/artifacts/sha256/3fdaef09e9e365c85e002737720f8d0ab8f278c1c244a2dde6a37663cf488ad4.headers.json +1 -0
  14. package/catalog/published/v1/artifacts/sha256/5f7e1d2b4020aec4e624f10815e9c8e88b3fa2be6122a349f0319613f76985ea +0 -0
  15. package/catalog/published/v1/artifacts/sha256/5f7e1d2b4020aec4e624f10815e9c8e88b3fa2be6122a349f0319613f76985ea.headers.json +1 -0
  16. package/catalog/published/v1/artifacts/sha256/69e3f6ccec3e14b465df60be577ca62f536251406b9a00cce019eac5252a2495 +4 -0
  17. package/catalog/published/v1/artifacts/sha256/69e3f6ccec3e14b465df60be577ca62f536251406b9a00cce019eac5252a2495.headers.json +1 -0
  18. package/catalog/published/v1/artifacts/sha256/73c393191ec41b2b40e0bcc8653d874cb42046023e7a72389bd54abf8df928b9.headers.json +1 -1
  19. package/catalog/published/v1/artifacts/sha256/80c5be6babfe03297069703410c3c29404dcf2525d2b128746bae5536f94831f +0 -0
  20. package/catalog/published/v1/artifacts/sha256/80c5be6babfe03297069703410c3c29404dcf2525d2b128746bae5536f94831f.headers.json +1 -0
  21. package/catalog/published/v1/artifacts/sha256/a140f34875990a080a84a61ac57e9ea924e7b8d79f0dd3a1b4242139cdd6a8e4 +0 -0
  22. package/catalog/published/v1/artifacts/sha256/a140f34875990a080a84a61ac57e9ea924e7b8d79f0dd3a1b4242139cdd6a8e4.headers.json +1 -0
  23. package/catalog/published/v1/artifacts/sha256/cba9aeb25bd2b45d01611a062df64db41b20302ffa81c4eb0d0d71b359e19e7c.headers.json +1 -1
  24. package/catalog/published/v1/artifacts/sha256/d96eba1f1881334d50ffc144276dfa98e147a415e7c591ecdfcb81680361dc91.headers.json +1 -1
  25. package/catalog/published/v1/artifacts/sha256/d9dddf83dfd2af98c525007dc494d564b9ffa1f5f393c564334dcb5137e99330 +1 -0
  26. package/catalog/published/v1/artifacts/sha256/d9dddf83dfd2af98c525007dc494d564b9ffa1f5f393c564334dcb5137e99330.headers.json +1 -0
  27. package/catalog/published/v1/artifacts/sha256/ec62dc96da0099b87f4511736c87309335527fb7031639493e06c95728dc8c54.headers.json +1 -1
  28. package/catalog/published/v1/artifacts/sha256/efba6a51e920b23f2bc24e72939fbf39adead922535b7a4e5a58af21f9289b22.headers.json +1 -1
  29. package/catalog/published/v1/catalog.json +1 -1
  30. package/catalog/published/v1/catalogs/sha256/ba2ed522ac281012a19ff5a94ca87b3bb4bdf230bb941220de5491513d2a57ea.json +1 -0
  31. package/catalog/published/v1/locks/sha256/09a6c49b4a0e11a111cecfcf6b4afd63b97efcb6439bed0401f60be6d9246780.json +1 -0
  32. package/catalog/published/v1/locks/sha256/4d2b82aaf0610359bf0e757e4e63100576687880f65d1fb093625301021fc61c.json +1 -0
  33. package/catalog/published/v1/locks/sha256/5292658503e248418d24e005942a3adc9ca79af135c8f8fa20659b048df93e0b.json +1 -0
  34. package/catalog/published/v1/locks/sha256/65e508124330a0cb0455ac06ce274ed9a47b7d720290b24146cf16f1153ad0a0.json +1 -0
  35. package/catalog/published/v1/locks/sha256/91621a0164f2a8d1a149034353e8679547e3269f7329a049006ae6e069fc41ff.json +1 -0
  36. package/catalog/published/v1/locks/sha256/bf650fdc44f5297175d58f3248b31768573a23484719f455c4e9c24502c41648.json +1 -0
  37. package/catalog/published/v1/locks/sha256/f5c7caac5e06541e998fa1c6d6ef1d50c8c146af161bcf5c1c0a828c0dcedaea.json +1 -0
  38. package/catalog/published/v1/locks/sha256/fba31e0e4cf54807389170be745fe505628d9af5cb2dc3223df1508bbde3973e.json +1 -0
  39. package/catalog/published/v1/manifests/sha256/026eef1bad61aa255defe8115c2daae7a739657aebe1cff1df31712d98606cd3.json +1 -0
  40. package/catalog/published/v1/manifests/sha256/03503c6f6d9142dce3f1d6eb5105b909fe0a2b16189bd58b9c8971ca588d3e61.json +1 -0
  41. package/catalog/published/v1/manifests/sha256/{2e038fc0a8ea054179c7ec22749b6b75d76e28f47bc49090ec4a92c62838efcb.json → 05781228ef0c812f61bf957a9ce2ee58544c4a6c010be46228ec8505a5a1f978.json} +1 -1
  42. package/catalog/published/v1/manifests/sha256/05781228ef0c812f61bf957a9ce2ee58544c4a6c010be46228ec8505a5a1f978.json.headers.json +1 -0
  43. package/catalog/published/v1/manifests/sha256/566b3535719ba2aa5f2f437939174189b7b195e161890a5b792667633e9d7fc5.json +1 -0
  44. package/catalog/published/v1/manifests/sha256/566b3535719ba2aa5f2f437939174189b7b195e161890a5b792667633e9d7fc5.json.headers.json +1 -0
  45. package/catalog/published/v1/manifests/sha256/64a81cb05980cf4e83f77244ee540b7fbc9e2e8bd8673614fff938a6a21de822.json +1 -0
  46. package/catalog/published/v1/manifests/sha256/64a81cb05980cf4e83f77244ee540b7fbc9e2e8bd8673614fff938a6a21de822.json.headers.json +1 -0
  47. package/catalog/published/v1/manifests/sha256/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e.json → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371.json} +1 -1
  48. package/catalog/published/v1/manifests/sha256/c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371.json.headers.json +1 -0
  49. package/catalog/published/v1/manifests/sha256/{9345a903a00ef5bcfd58c03f54e9d00670a1fb885284369b6af1c3fb0fcaa43a.json → e9c4da2392cebaa5377154b7fe0e72d7d943032e41cf775d0158df5808d1e38f.json} +1 -1
  50. package/catalog/published/v1/manifests/sha256/e9c4da2392cebaa5377154b7fe0e72d7d943032e41cf775d0158df5808d1e38f.json.headers.json +1 -0
  51. package/catalog/published/v1/manifests/sha256/fd500c4047bcddcc3d16d4a98ac7d1d0f887e0669a6ae8fa1f391a8b16a8abb9.json +1 -0
  52. package/catalog/published/v1/manifests/sha256/fd500c4047bcddcc3d16d4a98ac7d1d0f887e0669a6ae8fa1f391a8b16a8abb9.json.headers.json +1 -0
  53. package/dist/catalog-programs.d.ts +6 -0
  54. package/dist/catalog-programs.d.ts.map +1 -1
  55. package/dist/catalog-programs.js +15 -2
  56. package/dist/catalog-programs.js.map +1 -1
  57. package/dist/cli/cache-lifetime.d.ts +8 -0
  58. package/dist/cli/cache-lifetime.d.ts.map +1 -0
  59. package/dist/cli/cache-lifetime.js +214 -0
  60. package/dist/cli/cache-lifetime.js.map +1 -0
  61. package/dist/cli/cache-root.d.ts.map +1 -1
  62. package/dist/cli/cache-root.js +5 -1
  63. package/dist/cli/cache-root.js.map +1 -1
  64. package/dist/cli/main.d.ts +2 -0
  65. package/dist/cli/main.d.ts.map +1 -1
  66. package/dist/cli/main.js +82 -35
  67. package/dist/cli/main.js.map +1 -1
  68. package/dist/cli/package-packs.d.ts +1 -1
  69. package/dist/cli/package-packs.d.ts.map +1 -1
  70. package/dist/cli/package-packs.js +2 -68
  71. package/dist/cli/package-packs.js.map +1 -1
  72. package/dist/cli/project-build.d.ts.map +1 -1
  73. package/dist/cli/project-build.js +2 -0
  74. package/dist/cli/project-build.js.map +1 -1
  75. package/dist/cli/provision-catalog.d.ts +14 -0
  76. package/dist/cli/provision-catalog.d.ts.map +1 -1
  77. package/dist/cli/provision-catalog.js +59 -21
  78. package/dist/cli/provision-catalog.js.map +1 -1
  79. package/dist/image.d.ts +6 -0
  80. package/dist/image.d.ts.map +1 -1
  81. package/dist/image.js +14 -2
  82. package/dist/image.js.map +1 -1
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +6 -4
  85. package/dist/index.js.map +1 -1
  86. package/dist/shell/assets/{browser-sandbox-bridge-CRudVv29.js → browser-sandbox-bridge-Cxc4WXZV.js} +2 -2
  87. package/dist/shell/assets/{node-execution-worker-87fe3e3a28e9186d.js → node-execution-worker-08f9a46842d3ea2d.js} +4 -4
  88. package/dist/shell/assets/pythonHost-CTnI04xe.js +1 -0
  89. package/dist/shell/assets/{runtime-worker-DH_89R1c.js → runtime-worker-4ta5HAH6.js} +61 -12
  90. package/dist/shell/assets/{sandbox-BnR3tlyJ.js → sandbox-Csy8dHxD.js} +1 -1
  91. package/dist/shell/assets/{shell-5W27-rZ9.js → shell-BchR8dEr.js} +503 -502
  92. package/dist/shell/bundler-worker.js +4 -4
  93. package/dist/shell/index.html +2 -2
  94. package/dist/shell/node-execution-worker.js +1 -1
  95. package/dist/shell/python-host.html +1 -2
  96. package/dist/shell/sandbox.html +2 -2
  97. package/package.json +1 -1
  98. package/catalog/published/v1/catalogs/sha256/0706a928806dcf706b3ec1718fce04fde7637b0e3792860849ef2551fdc11207.json +0 -1
  99. package/catalog/published/v1/locks/sha256/0932882d27d284ca572fadc5139f8919a36fe4be5e7310f47518d32a490df0fe.json +0 -1
  100. package/catalog/published/v1/locks/sha256/17f8b244e6d8ee2441e204d88208fb12957019102df10b9387c2602b153568c2.json +0 -1
  101. package/catalog/published/v1/locks/sha256/4b762bf5e60a1ee7c2e837114286edc90b117fbfc4fdcf7988ffc78e8016d7e3.json +0 -1
  102. package/catalog/published/v1/locks/sha256/e847d9c9af2d5c30c7a60b362afb2d57a099782dbaa1d649b48a63f2fc22bd0d.json +0 -1
  103. package/catalog/published/v1/locks/sha256/ef55f6c4a945d35d7eac63950b6308b4fa2759f6826728c5fc9c6c195e238d44.json +0 -1
  104. package/catalog/published/v1/manifests/sha256/7c26dd6d9ba0fc4c5b0f224f7602b69ac10cdb98fadc12bb5c2b5337a5b7fc09.json +0 -1
  105. package/catalog/published/v1/manifests/sha256/ee2393699113b4be1eb965d37d9fa718fe6213980a33a3f08672457781f56415.json +0 -1
  106. package/dist/shell/assets/cross-origin-worker-host-BBpqGIrC.js +0 -1
  107. package/dist/shell/assets/index-DGXLsqoC.js +0 -1
  108. package/dist/shell/assets/pythonHost-xjkxJWzf.js +0 -1
  109. /package/catalog/published/v1/artifacts/sha256/{db018a9b080d9b5fbcc6e72bc47afbdae210b679902e8dd1a269b8bd24a1bcd6.headers.json → 1d9fc30121217ec1c2161e79c3df3e27047a786b8eb33ee6a218a887d794fa88.headers.json} +0 -0
  110. /package/catalog/published/v1/catalogs/sha256/{0706a928806dcf706b3ec1718fce04fde7637b0e3792860849ef2551fdc11207.json.headers.json → ba2ed522ac281012a19ff5a94ca87b3bb4bdf230bb941220de5491513d2a57ea.json.headers.json} +0 -0
  111. /package/catalog/published/v1/locks/sha256/{0932882d27d284ca572fadc5139f8919a36fe4be5e7310f47518d32a490df0fe.json.headers.json → 09a6c49b4a0e11a111cecfcf6b4afd63b97efcb6439bed0401f60be6d9246780.json.headers.json} +0 -0
  112. /package/catalog/published/v1/locks/sha256/{17f8b244e6d8ee2441e204d88208fb12957019102df10b9387c2602b153568c2.json.headers.json → 4d2b82aaf0610359bf0e757e4e63100576687880f65d1fb093625301021fc61c.json.headers.json} +0 -0
  113. /package/catalog/published/v1/locks/sha256/{4b762bf5e60a1ee7c2e837114286edc90b117fbfc4fdcf7988ffc78e8016d7e3.json.headers.json → 5292658503e248418d24e005942a3adc9ca79af135c8f8fa20659b048df93e0b.json.headers.json} +0 -0
  114. /package/catalog/published/v1/locks/sha256/{e847d9c9af2d5c30c7a60b362afb2d57a099782dbaa1d649b48a63f2fc22bd0d.json.headers.json → 65e508124330a0cb0455ac06ce274ed9a47b7d720290b24146cf16f1153ad0a0.json.headers.json} +0 -0
  115. /package/catalog/published/v1/locks/sha256/{ef55f6c4a945d35d7eac63950b6308b4fa2759f6826728c5fc9c6c195e238d44.json.headers.json → 91621a0164f2a8d1a149034353e8679547e3269f7329a049006ae6e069fc41ff.json.headers.json} +0 -0
  116. /package/catalog/published/v1/{manifests/sha256/2e038fc0a8ea054179c7ec22749b6b75d76e28f47bc49090ec4a92c62838efcb.json.headers.json → locks/sha256/bf650fdc44f5297175d58f3248b31768573a23484719f455c4e9c24502c41648.json.headers.json} +0 -0
  117. /package/catalog/published/v1/{manifests/sha256/606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e.json.headers.json → locks/sha256/f5c7caac5e06541e998fa1c6d6ef1d50c8c146af161bcf5c1c0a828c0dcedaea.json.headers.json} +0 -0
  118. /package/catalog/published/v1/{manifests/sha256/7c26dd6d9ba0fc4c5b0f224f7602b69ac10cdb98fadc12bb5c2b5337a5b7fc09.json.headers.json → locks/sha256/fba31e0e4cf54807389170be745fe505628d9af5cb2dc3223df1508bbde3973e.json.headers.json} +0 -0
  119. /package/catalog/published/v1/manifests/sha256/{9345a903a00ef5bcfd58c03f54e9d00670a1fb885284369b6af1c3fb0fcaa43a.json.headers.json → 026eef1bad61aa255defe8115c2daae7a739657aebe1cff1df31712d98606cd3.json.headers.json} +0 -0
  120. /package/catalog/published/v1/manifests/sha256/{ee2393699113b4be1eb965d37d9fa718fe6213980a33a3f08672457781f56415.json.headers.json → 03503c6f6d9142dce3f1d6eb5105b909fe0a2b16189bd58b9c8971ca588d3e61.json.headers.json} +0 -0
  121. /package/dist/shell/substrate/shell.compilers/{2e038fc0a8ea054179c7ec22749b6b75d76e28f47bc49090ec4a92c62838efcb → 05781228ef0c812f61bf957a9ce2ee58544c4a6c010be46228ec8505a5a1f978}/bindings_wasm_bg.wasm +0 -0
  122. /package/dist/shell/substrate/shell.compilers/{2e038fc0a8ea054179c7ec22749b6b75d76e28f47bc49090ec4a92c62838efcb → 05781228ef0c812f61bf957a9ce2ee58544c4a6c010be46228ec8505a5a1f978}/package.json +0 -0
  123. /package/dist/shell/substrate/shell.compilers/{2e038fc0a8ea054179c7ec22749b6b75d76e28f47bc49090ec4a92c62838efcb → 05781228ef0c812f61bf957a9ce2ee58544c4a6c010be46228ec8505a5a1f978}/rollup.browser.js +0 -0
  124. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/LICENSE.md +0 -0
  125. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/README.md +0 -0
  126. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/bin/esbuild +0 -0
  127. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/esbuild.wasm +0 -0
  128. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/esm/browser.d.ts +0 -0
  129. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/esm/browser.js +0 -0
  130. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/esm/browser.min.js +0 -0
  131. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/lib/browser.d.ts +0 -0
  132. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/lib/browser.js +0 -0
  133. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/lib/browser.min.js +0 -0
  134. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/lib/main.d.ts +0 -0
  135. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/lib/main.js +0 -0
  136. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/package.json +0 -0
  137. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/wasm_exec.js +0 -0
  138. /package/dist/shell/substrate/shell.compilers/{606b43db77c5b9be029cd31941d9ba4ac91424e9a5225ac62debb381e522979e → c5ebb33c1959713cf4dee3482dde9b95a87fe1c6dd9e947c013018254cd2a371}/wasm_exec_node.js +0 -0
  139. /package/dist/shell/substrate/shell.compilers/{9345a903a00ef5bcfd58c03f54e9d00670a1fb885284369b6af1c3fb0fcaa43a → e9c4da2392cebaa5377154b7fe0e72d7d943032e41cf775d0158df5808d1e38f}/amaro.js +0 -0
@@ -0,0 +1,4141 @@
1
+ // packages/cdp/dist/chromium-face.js
2
+ var encoder = new TextEncoder();
3
+ function chromiumFaceRunner(attachClient) {
4
+ return async (argv, options) => {
5
+ const name = argv[0]?.slice(argv[0].lastIndexOf("/") + 1) ?? "chrome";
6
+ if (!argv.includes("--remote-debugging-pipe")) {
7
+ return { stdout: "", stderr: `${name}: the tab's Chromium is driven over --remote-debugging-pipe only
8
+ `, exitCode: 1 };
9
+ }
10
+ const commands = options.descriptors?.[3];
11
+ const replies = options.descriptors?.[4];
12
+ if (!commands || !replies) {
13
+ return { stdout: "", stderr: `${name}: --remote-debugging-pipe needs pipes on fds 3 and 4
14
+ `, exitCode: 1 };
15
+ }
16
+ const messageListeners = [];
17
+ const closeListeners = [];
18
+ let open = true;
19
+ let finished;
20
+ const done = new Promise((resolve) => {
21
+ finished = resolve;
22
+ });
23
+ const end = () => {
24
+ if (!open)
25
+ return;
26
+ open = false;
27
+ replies.end();
28
+ for (const listener of closeListeners)
29
+ listener();
30
+ finished();
31
+ };
32
+ const peer = {
33
+ get readyState() {
34
+ return open ? 1 : 3;
35
+ },
36
+ send: (data) => {
37
+ if (open)
38
+ replies.write(encoder.encode(`${data}\0`));
39
+ },
40
+ close: () => end(),
41
+ on: ((event, listener) => {
42
+ if (event === "message")
43
+ messageListeners.push(listener);
44
+ else
45
+ closeListeners.push(listener);
46
+ })
47
+ };
48
+ options.signal?.addEventListener("abort", end, { once: true });
49
+ attachClient(peer);
50
+ void (async () => {
51
+ const decoder3 = new TextDecoder();
52
+ let buffered = "";
53
+ for await (const chunk of commands.input) {
54
+ buffered += decoder3.decode(chunk, { stream: true });
55
+ for (let boundary = buffered.indexOf("\0"); boundary >= 0; boundary = buffered.indexOf("\0")) {
56
+ const message = buffered.slice(0, boundary);
57
+ buffered = buffered.slice(boundary + 1);
58
+ const closing = browserClose(message);
59
+ if (closing !== void 0) {
60
+ peer.send(JSON.stringify({ id: closing, result: {} }));
61
+ end();
62
+ return;
63
+ }
64
+ for (const listener of messageListeners)
65
+ listener(message);
66
+ if (!open)
67
+ return;
68
+ }
69
+ }
70
+ end();
71
+ })().catch(end);
72
+ await done;
73
+ return { stdout: "", stderr: "", exitCode: 0 };
74
+ };
75
+ }
76
+ function browserClose(message) {
77
+ try {
78
+ const parsed = JSON.parse(message);
79
+ return parsed.method === "Browser.close" && parsed.sessionId === void 0 && typeof parsed.id === "number" ? parsed.id : void 0;
80
+ } catch {
81
+ return void 0;
82
+ }
83
+ }
84
+ async function playwrightChromiumRevisions(filesystem, roots) {
85
+ const revisions = /* @__PURE__ */ new Set();
86
+ for (const root of roots) {
87
+ const path = `${root.replace(/\/$/u, "")}/node_modules/playwright-core/browsers.json`;
88
+ if (!filesystem.existsSync(path))
89
+ continue;
90
+ try {
91
+ const pinned = JSON.parse(await filesystem.readFile(path, "utf8"));
92
+ for (const entry of pinned.browsers ?? []) {
93
+ if ((entry.name === "chromium" || entry.name === "chromium-headless-shell") && typeof entry.revision === "string")
94
+ revisions.add(entry.revision);
95
+ }
96
+ } catch {
97
+ }
98
+ }
99
+ return [...revisions];
100
+ }
101
+ async function placeChromiumFace(filesystem, cacheDirectory, revisions, baseline) {
102
+ for (const revision of revisions) {
103
+ if (!baseline.revisions.includes(revision))
104
+ throw new Error(`Playwright revision ${revision} has no qualified Chromium protocol face. Prepare its baseline on a host.`);
105
+ for (const placement of baseline.placements) {
106
+ const root = `${cacheDirectory}/${placement.directory}-${revision}`;
107
+ const executable = `${root}/${placement.executable}`;
108
+ filesystem.mkdirSync(executable.slice(0, executable.lastIndexOf("/")), { recursive: true });
109
+ const name = placement.executable.slice(placement.executable.lastIndexOf("/") + 1);
110
+ const stub = `#!/bin/sh
111
+ # ${name}: runtime-registered program
112
+ `;
113
+ let current;
114
+ try {
115
+ current = filesystem.existsSync(executable) ? await filesystem.readFile(executable, "utf8") : void 0;
116
+ } catch {
117
+ current = void 0;
118
+ }
119
+ if (current !== stub) {
120
+ filesystem.writeFileSync(executable, stub);
121
+ filesystem.chmodSync?.(executable, 493);
122
+ }
123
+ for (const marker of ["INSTALLATION_COMPLETE", "DEPENDENCIES_VALIDATED"]) {
124
+ if (!filesystem.existsSync(`${root}/${marker}`))
125
+ filesystem.writeFileSync(`${root}/${marker}`, "");
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ // node_modules/@volter/almostcdp/dist/browser-pages.js
132
+ var BrowserPages = class {
133
+ host;
134
+ hooks;
135
+ timeoutMs;
136
+ pages = /* @__PURE__ */ new Map();
137
+ stopped = false;
138
+ retired = /* @__PURE__ */ new Set();
139
+ constructor(host, hooks, timeoutMs) {
140
+ this.host = host;
141
+ this.hooks = hooks;
142
+ this.timeoutMs = timeoutMs;
143
+ }
144
+ context(id) {
145
+ return this.pages.get(id)?.contextId;
146
+ }
147
+ opener(id) {
148
+ return this.pages.get(id)?.openerId;
149
+ }
150
+ openerMark(id) {
151
+ return this.pages.get(id)?.openerMark;
152
+ }
153
+ ownMark(id) {
154
+ return this.pages.get(id)?.ownMark;
155
+ }
156
+ owner(id) {
157
+ return this.pages.get(id)?.owner;
158
+ }
159
+ acceptsSurface(id) {
160
+ if (this.retired.has(id))
161
+ return false;
162
+ const page = this.pages.get(id);
163
+ return !page || !page.controller.signal.aborted && !page.closing && !this.stopped && (!page.contextId || this.hooks.contextExists(page.contextId));
164
+ }
165
+ async create(owner, params, openerId, openerMark, ownMark) {
166
+ if (this.stopped)
167
+ throw new Error("Browser host is closing");
168
+ for (const key of Object.keys(params))
169
+ if (!["url", "browserContextId"].includes(key))
170
+ throw new Error(`Unsupported page option: ${key}`);
171
+ if (typeof params.url !== "string")
172
+ throw new Error("url must be a string");
173
+ if (params.browserContextId !== void 0 && typeof params.browserContextId !== "string")
174
+ throw new Error("browserContextId must be a string");
175
+ const contextId = params.browserContextId;
176
+ if (contextId !== void 0 && !this.hooks.contextExists(contextId))
177
+ throw new Error("No browser context with given id");
178
+ const page = { id: crypto.randomUUID().replaceAll("-", ""), contextId, ...openerId ? { openerId } : {}, ...openerMark ? { openerMark } : {}, ...ownMark ? { ownMark } : {}, owner, controller: new AbortController(), ready: false, settled: false };
179
+ this.pages.set(page.id, page);
180
+ const timer = setTimeout(() => page.controller.abort(new Error("Page creation timed out")), this.timeoutMs);
181
+ let onAbort = () => {
182
+ };
183
+ const aborted = new Promise((_, reject) => {
184
+ onAbort = () => {
185
+ this.hooks.disconnectSurface(page.id);
186
+ reject(page.controller.signal.reason);
187
+ };
188
+ page.controller.signal.addEventListener("abort", onAbort, { once: true });
189
+ });
190
+ const completed = Promise.resolve().then(() => this.host.createPage({
191
+ id: page.id,
192
+ url: params.url,
193
+ ...contextId !== void 0 ? { browserContextId: contextId } : {}
194
+ }, page.controller.signal)).then(async () => {
195
+ page.settled = true;
196
+ if (!this.acceptsSurface(page.id) || !this.hooks.surfaceExists(page.id)) {
197
+ const error = page.controller.signal.reason ?? new Error("Host did not register a live page surface");
198
+ page.controller.abort(error);
199
+ await this.closeEntry(page);
200
+ throw error;
201
+ }
202
+ page.ready = true;
203
+ return page.id;
204
+ }, async (error) => {
205
+ page.settled = true;
206
+ page.controller.abort(error);
207
+ await this.closeEntry(page);
208
+ throw error;
209
+ });
210
+ try {
211
+ return await Promise.race([completed, aborted]);
212
+ } finally {
213
+ clearTimeout(timer);
214
+ page.controller.signal.removeEventListener("abort", onAbort);
215
+ }
216
+ }
217
+ closeEntry(page) {
218
+ if (!page.closing)
219
+ page.closing = Promise.resolve().then(() => this.host.closePage(page.id)).then(() => {
220
+ this.hooks.disconnectSurface(page.id);
221
+ this.retired.add(page.id);
222
+ this.pages.delete(page.id);
223
+ }, (error) => {
224
+ page.closing = void 0;
225
+ throw error;
226
+ });
227
+ return page.closing;
228
+ }
229
+ async closeTarget(id) {
230
+ const page = this.pages.get(id);
231
+ if (!page?.ready || !this.hooks.surfaceExists(id))
232
+ throw new Error("No managed page with given id");
233
+ await this.closeEntry(page);
234
+ }
235
+ disconnect(owner) {
236
+ for (const page of this.pages.values())
237
+ if (page.owner === owner && !page.ready)
238
+ page.controller.abort(new Error("Page creator disconnected"));
239
+ }
240
+ async disposeContext(id) {
241
+ const closing = [];
242
+ for (const page of this.pages.values())
243
+ if (page.contextId === id) {
244
+ if (!page.ready)
245
+ page.controller.abort(new Error("Browser context disposed"));
246
+ if (page.settled)
247
+ closing.push(this.closeEntry(page));
248
+ }
249
+ await Promise.all(closing);
250
+ }
251
+ async close() {
252
+ this.stopped = true;
253
+ const closing = [];
254
+ for (const page of this.pages.values()) {
255
+ if (!page.ready)
256
+ page.controller.abort(new Error("Browser host is closing"));
257
+ if (page.settled)
258
+ closing.push(this.closeEntry(page));
259
+ }
260
+ await Promise.all(closing);
261
+ }
262
+ };
263
+
264
+ // node_modules/@volter/almostcdp/dist/browser-contexts.js
265
+ var BrowserContexts = class {
266
+ host;
267
+ timeoutMs;
268
+ contexts = /* @__PURE__ */ new Map();
269
+ closed = false;
270
+ constructor(host, timeoutMs) {
271
+ this.host = host;
272
+ this.timeoutMs = timeoutMs;
273
+ }
274
+ ids() {
275
+ return [...this.contexts.values()].filter((c) => c.ready && !c.disposal).map((c) => c.id);
276
+ }
277
+ async create(owner, params) {
278
+ if (this.closed)
279
+ throw new Error("Browser host is closing");
280
+ const supported = /* @__PURE__ */ new Set(["disposeOnDetach", "proxyServer", "proxyBypassList", "originsWithUniversalNetworkAccess"]);
281
+ for (const key of Object.keys(params))
282
+ if (!supported.has(key))
283
+ throw new Error(`Unsupported context option: ${key}`);
284
+ if (params.disposeOnDetach !== void 0 && typeof params.disposeOnDetach !== "boolean")
285
+ throw new Error("disposeOnDetach must be boolean");
286
+ for (const key of ["proxyServer", "proxyBypassList"])
287
+ if (params[key] !== void 0 && typeof params[key] !== "string")
288
+ throw new Error(`${key} must be a string`);
289
+ const origins = params.originsWithUniversalNetworkAccess;
290
+ if (origins !== void 0 && (!Array.isArray(origins) || !origins.every((value) => typeof value === "string")))
291
+ throw new Error("originsWithUniversalNetworkAccess must be a string array");
292
+ const c = { id: crypto.randomUUID().replaceAll("-", ""), owner, disposeOnDetach: params.disposeOnDetach === true, controller: new AbortController(), ready: false, settled: false };
293
+ this.contexts.set(c.id, c);
294
+ const timer = setTimeout(() => c.controller.abort(new Error("Context creation timed out")), this.timeoutMs);
295
+ const creation = Promise.resolve().then(() => this.host.createContext({
296
+ id: c.id,
297
+ ...params.proxyServer !== void 0 ? { proxyServer: params.proxyServer } : {},
298
+ ...params.proxyBypassList !== void 0 ? { proxyBypassList: params.proxyBypassList } : {},
299
+ ...origins !== void 0 ? { originsWithUniversalNetworkAccess: origins } : {}
300
+ }, c.controller.signal));
301
+ const completed = creation.then(async () => {
302
+ c.settled = true;
303
+ if (c.controller.signal.aborted || this.closed) {
304
+ await this.disposeEntry(c);
305
+ throw c.controller.signal.reason ?? new Error("Browser host is closing");
306
+ }
307
+ c.ready = true;
308
+ return c.id;
309
+ }, async (error) => {
310
+ c.settled = true;
311
+ await this.disposeEntry(c);
312
+ throw error;
313
+ });
314
+ let onAbort = () => {
315
+ };
316
+ const aborted = new Promise((_, reject) => {
317
+ onAbort = () => reject(c.controller.signal.reason);
318
+ c.controller.signal.addEventListener("abort", onAbort, { once: true });
319
+ });
320
+ try {
321
+ return await Promise.race([completed, aborted]);
322
+ } finally {
323
+ clearTimeout(timer);
324
+ c.controller.signal.removeEventListener("abort", onAbort);
325
+ }
326
+ }
327
+ disposeEntry(c) {
328
+ if (!c.disposal) {
329
+ c.disposal = Promise.resolve().then(() => this.host.disposeContext(c.id)).then(() => {
330
+ this.contexts.delete(c.id);
331
+ }, (error) => {
332
+ c.disposal = void 0;
333
+ throw error;
334
+ });
335
+ }
336
+ return c.disposal;
337
+ }
338
+ async dispose(id) {
339
+ const c = this.contexts.get(id);
340
+ if (!c?.ready)
341
+ throw new Error("No browser context with given id");
342
+ await this.disposeEntry(c);
343
+ }
344
+ async disconnect(owner) {
345
+ const disposals = [];
346
+ for (const c of this.contexts.values()) {
347
+ if (c.owner !== owner)
348
+ continue;
349
+ if (!c.ready)
350
+ c.controller.abort(new Error("Context creator disconnected"));
351
+ else if (c.disposeOnDetach)
352
+ disposals.push(this.disposeEntry(c));
353
+ }
354
+ await Promise.all(disposals);
355
+ }
356
+ async close() {
357
+ this.closed = true;
358
+ for (const c of this.contexts.values())
359
+ if (!c.ready)
360
+ c.controller.abort(new Error("Browser host is closing"));
361
+ await Promise.all([...this.contexts.values()].filter((c) => c.settled).map((c) => this.disposeEntry(c)));
362
+ }
363
+ };
364
+
365
+ // node_modules/@volter/almostcdp/dist/protocol.js
366
+ var unsupported = (method) => ({
367
+ code: -32601,
368
+ message: `'${method}' wasn't found`,
369
+ data: "AlmostCDP does not implement this command for the connected surface"
370
+ });
371
+
372
+ // node_modules/@volter/almostcdp/dist/surface-module.js
373
+ var base64ToBytes = (value) => {
374
+ const binary = atob(value);
375
+ const bytes = new Uint8Array(binary.length);
376
+ for (let index = 0; index < binary.length; index++)
377
+ bytes[index] = binary.charCodeAt(index);
378
+ return bytes;
379
+ };
380
+ var bytesToBase64 = (bytes) => {
381
+ let binary = "";
382
+ for (let index = 0; index < bytes.length; index += 32768) {
383
+ binary += String.fromCharCode(...bytes.subarray(index, index + 32768));
384
+ }
385
+ return btoa(binary);
386
+ };
387
+
388
+ // node_modules/@volter/almostcdp/dist/cookies.js
389
+ var FAN_OUT_PARAM = "almostcdpFanOut";
390
+
391
+ // node_modules/@volter/almostcdp/dist/downloads.js
392
+ var DOWNLOAD_POLICY_PARAM = "almostcdpDownloadPolicy";
393
+
394
+ // node_modules/@volter/almostcdp/dist/file-chooser.js
395
+ var FILE_PAYLOADS_PARAM = "almostcdpFiles";
396
+
397
+ // node_modules/@volter/almostcdp/dist/support.js
398
+ var SUPPORT_MANIFEST = {
399
+ adapters: {
400
+ accessibility: "optional",
401
+ capture: "optional",
402
+ browserContexts: "optional-host-owned-storage-isolation",
403
+ browserPages: "optional-host-owned-page-lifecycle",
404
+ rrwebCast: "optional",
405
+ surface: "same-origin DOM"
406
+ },
407
+ commands: {
408
+ hostContext: ["Target.createBrowserContext", "Target.disposeBrowserContext"],
409
+ hostPage: ["Target.createTarget", "Target.closeTarget"],
410
+ browser: [
411
+ "Browser.cancelDownload",
412
+ "Browser.getBrowserCommandLine",
413
+ "Browser.getVersion",
414
+ "Browser.setDownloadBehavior",
415
+ "Browser.grantPermissions",
416
+ "Browser.resetPermissions",
417
+ "Storage.clearCookies",
418
+ "Storage.getCookies",
419
+ "Storage.setCookies",
420
+ "Target.activateTarget",
421
+ "Target.attachToTarget",
422
+ "Target.attachToBrowserTarget",
423
+ "Target.detachFromTarget",
424
+ "Target.getTargetInfo",
425
+ "Target.getBrowserContexts",
426
+ "Target.getTargets",
427
+ "Target.setAutoAttach",
428
+ "Target.setDiscoverTargets"
429
+ ],
430
+ surface: [
431
+ "Accessibility.disable",
432
+ "Browser.cancelDownload",
433
+ "Browser.grantPermissions",
434
+ "Browser.resetPermissions",
435
+ "Browser.setDownloadBehavior",
436
+ "Accessibility.enable",
437
+ "Accessibility.getChildAXNodes",
438
+ "Accessibility.getFullAXTree",
439
+ "Accessibility.getPartialAXTree",
440
+ "Accessibility.getRootAXNode",
441
+ "Accessibility.queryAXTree",
442
+ "DOM.describeNode",
443
+ "DOM.disable",
444
+ "DOM.enable",
445
+ "DOM.focus",
446
+ "DOM.getBoxModel",
447
+ "DOM.getContentQuads",
448
+ "DOM.getDocument",
449
+ "DOM.querySelector",
450
+ "DOM.querySelectorAll",
451
+ "DOM.requestNode",
452
+ "DOM.requestChildNodes",
453
+ "DOM.resolveNode",
454
+ "DOM.scrollIntoViewIfNeeded",
455
+ "DOM.setFileInputFiles",
456
+ "Emulation.clearDeviceMetricsOverride",
457
+ "Emulation.clearGeolocationOverride",
458
+ "Audits.disable",
459
+ "Audits.enable",
460
+ "Performance.disable",
461
+ "Performance.enable",
462
+ "Performance.getMetrics",
463
+ "Emulation.setDeviceMetricsOverride",
464
+ "Emulation.setEmulatedMedia",
465
+ "Emulation.setFocusEmulationEnabled",
466
+ "Emulation.setGeolocationOverride",
467
+ "Emulation.setLocaleOverride",
468
+ "Emulation.setTouchEmulationEnabled",
469
+ "Emulation.setUserAgentOverride",
470
+ "Fetch.continueRequest",
471
+ "Fetch.disable",
472
+ "Fetch.enable",
473
+ "Fetch.failRequest",
474
+ "Fetch.fulfillRequest",
475
+ "Input.dispatchDragEvent",
476
+ "Input.dispatchKeyEvent",
477
+ "Input.dispatchMouseEvent",
478
+ "Input.dispatchTouchEvent",
479
+ "Input.emulateTouchFromMouseEvent",
480
+ "Input.imeSetComposition",
481
+ "Input.insertText",
482
+ "Input.setInterceptDrags",
483
+ "Log.disable",
484
+ "Log.enable",
485
+ "Network.disable",
486
+ "Network.enable",
487
+ "Network.getResponseBody",
488
+ "Network.setCacheDisabled",
489
+ "Network.setExtraHTTPHeaders",
490
+ "Page.addScriptToEvaluateOnNewDocument",
491
+ "Page.bringToFront",
492
+ "Page.captureScreenshot",
493
+ "Page.close",
494
+ "Page.createIsolatedWorld",
495
+ "Page.deleteCookie",
496
+ "Page.disable",
497
+ "Page.enable",
498
+ "Page.getFrameTree",
499
+ "Page.handleJavaScriptDialog",
500
+ "Page.getLayoutMetrics",
501
+ "Page.getNavigationHistory",
502
+ "Page.getResourceTree",
503
+ "Page.navigate",
504
+ "Page.navigateToHistoryEntry",
505
+ "Page.reload",
506
+ "Page.removeScriptToEvaluateOnNewDocument",
507
+ "Page.setFontFamilies",
508
+ "Page.setInterceptFileChooserDialog",
509
+ "Page.screencastFrameAck",
510
+ "Page.setLifecycleEventsEnabled",
511
+ "Page.setTouchEmulationEnabled",
512
+ "Page.startScreencast",
513
+ "Page.stopScreencast",
514
+ "Runtime.addBinding",
515
+ "Runtime.callFunctionOn",
516
+ "Runtime.disable",
517
+ "Runtime.enable",
518
+ "Runtime.evaluate",
519
+ "Runtime.getProperties",
520
+ "Runtime.releaseObject",
521
+ "Runtime.releaseObjectGroup",
522
+ "Runtime.runIfWaitingForDebugger",
523
+ "Schema.getDomains",
524
+ "Storage.clearCookies",
525
+ "Storage.getCookies",
526
+ "Storage.setCookies",
527
+ "Target.activateTarget",
528
+ "Target.setAutoAttach"
529
+ ],
530
+ /** Answered with a named protocol error: the surface cannot perform them. */
531
+ refused: [
532
+ "Browser.getWindowForTarget",
533
+ "Emulation.setDefaultBackgroundColorOverride",
534
+ "Emulation.setOrientationOverride",
535
+ "Emulation.setScriptExecutionDisabled",
536
+ "Emulation.setTimezoneOverride",
537
+ "Fetch.continueWithAuth",
538
+ "Network.clearBrowserCache",
539
+ "Network.emulateNetworkConditions",
540
+ "Network.loadNetworkResource",
541
+ "Network.setBypassServiceWorker",
542
+ "Page.setBypassCSP",
543
+ "Security.setIgnoreCertificateErrors"
544
+ ]
545
+ },
546
+ /**
547
+ * Refused by name because no backend exists (tracing, profiling, debugging,
548
+ * CSS inspection, streams, printing, browser windows, child frames). The
549
+ * listed commands are the ones the pinned Playwright sends only on request.
550
+ */
551
+ unimplemented: {
552
+ domains: ["CSS", "Debugger", "HeapProfiler", "IO", "Profiler", "Tracing"],
553
+ commands: [
554
+ "Browser.close",
555
+ "Browser.getWindowBounds",
556
+ "Browser.setDockTile",
557
+ "Browser.setWindowBounds",
558
+ "Console.enable",
559
+ "DOM.getFrameOwner",
560
+ "Page.printToPDF",
561
+ "Target.sendMessageToTarget"
562
+ ]
563
+ },
564
+ fidelity: {
565
+ dialogs: "reported-and-dismissed-synchronously-while-Page-is-observed; accept refused",
566
+ downloads: "anchor downloads (a[download], blob:, data:) intercepted by the surface and written through the host; downloads the host browser starts from an attachment navigation are its own",
567
+ capture: "adapter-declared",
568
+ deviceScaleFactor: "js-visible-only",
569
+ history: "frame-scoped via the Navigation API; without it, agent-observed entries and traversal refused",
570
+ pageClose: "host-hook-required",
571
+ urls: "host-mapped; page script sees the frame's real location",
572
+ userAgent: "js-visible-only; HTTP headers unchanged; navigator.platform only from the platform parameter",
573
+ locale: "js-visible-only; Intl constructors, toLocale*String and navigator.language(s)",
574
+ geolocation: "js-visible-only; navigator.geolocation answers only while geolocation is granted",
575
+ permissions: "js-visible-only; navigator.permissions.query and Notification.permission; host prompts unchanged",
576
+ touch: "js-visible-only; navigator.maxTouchPoints and ontouchstart",
577
+ realmOverrides: "installed when a document finishes loading; scripts that run earlier see host values",
578
+ cookies: "script-visible-only; HttpOnly cookies unreadable and unsettable; each surface reaches its own host and path",
579
+ fileChooser: "click-and-showPicker-interception; files assigned only through a host file adapter",
580
+ fontFamilies: "standard-applied; generic families resolved by host browser; forScripts ignored",
581
+ screenSize: "js-visible-only",
582
+ viewport: "frame-element-resized; a top-level surface accepts only its current size",
583
+ input: "synthetic-isTrusted-false",
584
+ dragAndDrop: "synthetic-DataTransfer; draggable-element drags only (no text-selection or file drags)",
585
+ keyboardEditing: "text-controls-code-point-movement-and-deletion; no-platform-word-or-grapheme-segmentation",
586
+ legacyTouchEmulation: "mouse-events-only",
587
+ wheel: "synthetic-cancelable-event-and-scrollable-ancestor-default-action",
588
+ network: "fetch-and-xhr-observation",
589
+ interception: "fetch-and-xhr-only; synchronous XHR and response-stage interception unavailable; authentication challenges stay with the host browser",
590
+ responseBodies: "fetch-and-xhr; 8-MiB-LRU; json/document XHR bodies unavailable",
591
+ cacheDisabled: "fetch-requests-use-no-store; XHR, navigation and subresources keep the HTTP cache",
592
+ rrweb: "side-channel"
593
+ },
594
+ protocol: {
595
+ devtoolsProtocolPackage: "0.0.1702366",
596
+ protocolVersion: "1.3"
597
+ },
598
+ version: 1
599
+ };
600
+
601
+ // node_modules/@volter/almostcdp/dist/unavailable.js
602
+ var UNAVAILABLE_DOMAINS = {
603
+ CSS: "no style-engine inspection backend; stylesheets and rule usage belong to the host browser",
604
+ Debugger: "no script debugger backend; scripts run in the host browser's engine",
605
+ HeapProfiler: "no heap inspection or garbage-collection backend",
606
+ IO: "no protocol stream backend; nothing produces stream handles",
607
+ Profiler: "no CPU profiling or coverage backend",
608
+ Tracing: "no tracing backend"
609
+ };
610
+ var UNAVAILABLE_COMMANDS = {
611
+ "Browser.cancelDownload": "downloads are not modelled without a host download adapter",
612
+ "Browser.close": "the browser belongs to the embedding host",
613
+ "Browser.getWindowBounds": "a surface has no browser window",
614
+ "Browser.getWindowForTarget": "a surface has no browser window",
615
+ "Browser.setDockTile": "a surface has no browser window",
616
+ "Browser.setWindowBounds": "a surface has no browser window",
617
+ "Console.enable": "console messages are reported through Runtime and Log",
618
+ "DOM.getFrameOwner": "child frames are not modelled",
619
+ "Emulation.setDefaultBackgroundColorOverride": "the page background is painted by the host browser's compositor",
620
+ "Emulation.setScriptExecutionDisabled": "script execution is controlled by the host browser",
621
+ "Emulation.setTimezoneOverride": "the time zone of Date and Intl is fixed by the host browser's realm",
622
+ "Network.clearBrowserCache": "the HTTP cache belongs to the host browser",
623
+ "Network.emulateNetworkConditions": "network conditions are applied by the host browser's network stack",
624
+ "Network.loadNetworkResource": "resources outside the page's own fetch are loaded by the host browser",
625
+ "Network.setBypassServiceWorker": "service-worker routing belongs to the host browser",
626
+ "Page.printToPDF": "no print or PDF backend",
627
+ "Page.setBypassCSP": "Content-Security-Policy is enforced by the host browser",
628
+ "Security.setIgnoreCertificateErrors": "certificate validation belongs to the host browser",
629
+ "Target.sendMessageToTarget": "sessions are flattened; nested message routing is not modelled"
630
+ };
631
+ var unavailableCommand = (method) => {
632
+ const why = UNAVAILABLE_COMMANDS[method] ?? UNAVAILABLE_DOMAINS[method.split(".")[0]];
633
+ return why === void 0 ? void 0 : { code: -32e3, message: `${method} is unavailable on a DOM surface: ${why}` };
634
+ };
635
+
636
+ // node_modules/@volter/almostcdp/dist/discovery.js
637
+ function discoveryDocument(source, address, pathname) {
638
+ const path = pathname.length > 1 ? pathname.replace(/\/$/, "") : pathname;
639
+ const wsOrigin = /^wss?:\/\//u.test(address) ? address : `ws://${address}`;
640
+ const hostAddress = wsOrigin.replace(/^wss?:\/\//u, "");
641
+ if (path === "/json/version") {
642
+ return {
643
+ status: 200,
644
+ body: {
645
+ "Browser": "AlmostCDP/0.1.0",
646
+ "Protocol-Version": "1.3",
647
+ "User-Agent": "AlmostCDP/0.1.0",
648
+ "V8-Version": "0",
649
+ "WebKit-Version": "0",
650
+ "webSocketDebuggerUrl": `${wsOrigin}/devtools/browser/${source.browserId}`
651
+ }
652
+ };
653
+ }
654
+ if (path === "/json" || path === "/json/list") {
655
+ return {
656
+ status: 200,
657
+ body: source.targets().map((surface) => ({
658
+ description: "",
659
+ devtoolsFrontendUrl: `/devtools/inspector.html?${wsOrigin.startsWith("wss:") ? "wss" : "ws"}=${hostAddress}/devtools/page/${surface.id}`,
660
+ id: surface.id,
661
+ title: surface.title,
662
+ type: surface.type,
663
+ url: surface.url,
664
+ webSocketDebuggerUrl: `${wsOrigin}/devtools/page/${surface.id}`
665
+ }))
666
+ };
667
+ }
668
+ if (path === "/health")
669
+ return { status: 200, body: { ok: true, surfaces: source.targets().length } };
670
+ if (path === "/compatibility")
671
+ return { status: 200, body: source.compatibility() };
672
+ return { status: 404, body: { error: "not found" } };
673
+ }
674
+
675
+ // node_modules/@volter/almostcdp/dist/dispatcher.js
676
+ var SubsurfacePeer = class {
677
+ deliver;
678
+ readyState = 1;
679
+ listeners = { message: [], close: [] };
680
+ constructor(deliver) {
681
+ this.deliver = deliver;
682
+ }
683
+ send(data) {
684
+ if (this.readyState === 1)
685
+ this.deliver({ data });
686
+ }
687
+ close() {
688
+ if (this.readyState !== 1)
689
+ return;
690
+ this.deliver({ close: true });
691
+ this.closed();
692
+ }
693
+ on(event, listener) {
694
+ this.listeners[event].push(listener);
695
+ }
696
+ received(data) {
697
+ if (this.readyState === 1)
698
+ for (const listener of this.listeners.message)
699
+ listener(data);
700
+ }
701
+ closed() {
702
+ if (this.readyState === 3)
703
+ return;
704
+ this.readyState = 3;
705
+ for (const listener of this.listeners.close)
706
+ listener();
707
+ }
708
+ };
709
+ var stagedSequence = 0;
710
+ var json = (ws, value) => {
711
+ if ((ws.bufferedAmount ?? 0) > 8 * 1024 * 1024) {
712
+ ws.close(1013, "Consumer too slow; reconnect for a fresh snapshot");
713
+ return;
714
+ }
715
+ if (ws.readyState === 1)
716
+ ws.send(JSON.stringify(value));
717
+ };
718
+ var targetInfo = (surface) => ({
719
+ attached: false,
720
+ browserContextId: surface.contextId ?? "default",
721
+ canAccessOpener: false,
722
+ targetId: surface.description.id,
723
+ title: surface.description.title,
724
+ type: surface.description.type,
725
+ url: surface.description.url
726
+ });
727
+ function createDispatcher(options) {
728
+ const { browserId, surfaceToken } = options;
729
+ const surfaces = /* @__PURE__ */ new Map();
730
+ const subsurfaceParents = /* @__PURE__ */ new WeakMap();
731
+ const autoAttaches = (client, parent, surface) => {
732
+ if (!parent)
733
+ return surface.parentId === void 0;
734
+ return surface.parentId !== void 0 && client.sessions.get(parent) === surface.parentId;
735
+ };
736
+ const successions = /* @__PURE__ */ new Map();
737
+ const successionGraceMs = options.successionGraceMs ?? 15e3;
738
+ const contexts = options.contextHost ? new BrowserContexts({
739
+ createContext: (params, signal) => options.contextHost.createContext(params, signal),
740
+ disposeContext: async (id) => {
741
+ await pages?.disposeContext(id);
742
+ await options.contextHost.disposeContext(id);
743
+ }
744
+ }, options.commandTimeoutMs ?? 15e3) : void 0;
745
+ const pages = options.pageHost ? new BrowserPages(options.pageHost, {
746
+ contextExists: (id) => contexts?.ids().includes(id) ?? false,
747
+ surfaceExists: (id) => surfaces.get(id)?.ws.readyState === 1,
748
+ disconnectSurface: (id) => surfaces.get(id)?.ws.close(1001, "Managed page closed")
749
+ }, options.commandTimeoutMs ?? 15e3) : void 0;
750
+ const clients = /* @__PURE__ */ new Set();
751
+ const pageInfo = (surface) => {
752
+ const openerId = pages?.opener(surface.description.id);
753
+ return { ...targetInfo(surface), ...openerId ? { openerId, canAccessOpener: pages?.openerMark(surface.description.id) !== void 0 } : {} };
754
+ };
755
+ const castViewers = /* @__PURE__ */ new Map();
756
+ const pending = /* @__PURE__ */ new Map();
757
+ const commandCounts = /* @__PURE__ */ new Map();
758
+ const screencastOwners = /* @__PURE__ */ new Map();
759
+ const contextGrants = /* @__PURE__ */ new Map();
760
+ const downloadPolicies = /* @__PURE__ */ new Map();
761
+ const downloadsInFlight = /* @__PURE__ */ new Map();
762
+ const policyCommand = (policy) => ({
763
+ id: 0,
764
+ method: "Browser.setDownloadBehavior",
765
+ params: { [DOWNLOAD_POLICY_PARAM]: true, behavior: policy.behavior }
766
+ });
767
+ let relaySequence = 0;
768
+ const sendResponse = (client, command, result = {}) => {
769
+ const response = { id: command.id, result };
770
+ if (command.sessionId)
771
+ response.sessionId = command.sessionId;
772
+ json(client.ws, response);
773
+ };
774
+ const sendError = (client, command, error = unsupported(command.method)) => {
775
+ const response = { error, id: command.id };
776
+ if (command.sessionId)
777
+ response.sessionId = command.sessionId;
778
+ json(client.ws, response);
779
+ };
780
+ const attach = (client, surface, parentSessionId) => {
781
+ const sessionId = globalThis.crypto.randomUUID().replaceAll("-", "");
782
+ client.sessions.set(sessionId, surface.description.id);
783
+ client.parents.set(sessionId, parentSessionId ?? "");
784
+ const event = {
785
+ method: "Target.attachedToTarget",
786
+ ...parentSessionId ? { sessionId: parentSessionId } : {},
787
+ params: {
788
+ sessionId,
789
+ targetInfo: { ...pageInfo(surface), attached: true },
790
+ waitingForDebugger: false
791
+ }
792
+ };
793
+ json(client.ws, event);
794
+ return sessionId;
795
+ };
796
+ const forward = (client, command, surface) => {
797
+ const files = command.params?.files;
798
+ if (command.method === "DOM.setFileInputFiles" && options.fileHost && Array.isArray(files) && files.length && command.params?.[FILE_PAYLOADS_PARAM] === void 0) {
799
+ const host = options.fileHost;
800
+ const token = `files-${++stagedSequence}`;
801
+ void (async () => {
802
+ const paths = [];
803
+ for (const named of files) {
804
+ const path = String(named);
805
+ const entries = await host.list?.(path);
806
+ if (!entries) {
807
+ paths.push({ path });
808
+ continue;
809
+ }
810
+ const base = path.replace(/[\\/]+$/u, "");
811
+ const folder = base.split(/[\\/]/u).pop() ?? base;
812
+ for (const entry of entries)
813
+ paths.push({ path: `${base}/${entry}`, relativePath: `${folder}/${entry}` });
814
+ }
815
+ for (const [index, { path, relativePath }] of paths.entries()) {
816
+ const file = await host.read(path);
817
+ json(surface.ws, {
818
+ type: "stage-file",
819
+ token,
820
+ index,
821
+ name: file.name ?? path.split(/[\\/]/u).pop() ?? path,
822
+ ...file.type ? { mimeType: file.type } : {},
823
+ ...file.lastModified !== void 0 ? { lastModified: file.lastModified } : {},
824
+ ...relativePath ? { relativePath } : {},
825
+ dataBase64: bytesToBase64(file.data)
826
+ });
827
+ }
828
+ return paths.length;
829
+ })().then((count) => forward(client, { ...command, params: { ...command.params, files: [], [FILE_PAYLOADS_PARAM]: { token, count } } }, surface), (error) => sendError(client, command, { code: -32e3, message: error instanceof Error ? error.message : String(error) }));
830
+ return;
831
+ }
832
+ const key = command.sessionId ?? "direct";
833
+ const domain = command.method.split(".")[0];
834
+ let settleSubscription;
835
+ if (command.method === `${domain}.enable` || command.method === `${domain}.disable`) {
836
+ const domains = client.subscriptionChanges.get(key) ?? /* @__PURE__ */ new Map();
837
+ client.subscriptionChanges.set(key, domains);
838
+ const state = domains.get(domain) ?? { enabled: client.subscriptions.get(key)?.has(domain) ?? false, changes: [] };
839
+ domains.set(domain, state);
840
+ const change = { enabled: command.method.endsWith(".enable") };
841
+ state.changes.push(change);
842
+ const update = () => {
843
+ let enabled = state.enabled;
844
+ for (const pending2 of state.changes)
845
+ if (pending2.accepted !== false)
846
+ enabled = pending2.enabled;
847
+ const subscriptions = client.subscriptions.get(key) ?? /* @__PURE__ */ new Set();
848
+ if (enabled)
849
+ subscriptions.add(domain);
850
+ else
851
+ subscriptions.delete(domain);
852
+ client.subscriptions.set(key, subscriptions);
853
+ };
854
+ update();
855
+ settleSubscription = (accepted) => {
856
+ if (client.subscriptionChanges.get(key)?.get(domain) !== state)
857
+ return;
858
+ change.accepted = accepted;
859
+ while (state.changes[0]?.accepted !== void 0) {
860
+ const completed = state.changes.shift();
861
+ if (completed.accepted)
862
+ state.enabled = completed.enabled;
863
+ }
864
+ update();
865
+ if (!state.changes.length) {
866
+ domains.delete(domain);
867
+ if (!domains.size)
868
+ client.subscriptionChanges.delete(key);
869
+ }
870
+ };
871
+ }
872
+ if (command.method === `${domain}.disable`) {
873
+ const stillObserved = [...clients].some((other) => [...other.subscriptions].some(([sessionId, domains]) => domains.has(domain) && (sessionId === "direct" ? other.directSurfaceId : other.sessions.get(sessionId)) === surface.description.id));
874
+ if (stillObserved && domain !== "Runtime" && domain !== "DOM" && domain !== "Fetch") {
875
+ settleSubscription?.(true);
876
+ sendResponse(client, command);
877
+ return;
878
+ }
879
+ }
880
+ let settleCapture;
881
+ if (command.method.startsWith("Page.") && ["Page.startScreencast", "Page.stopScreencast", "Page.screencastFrameAck"].includes(command.method)) {
882
+ const owner = screencastOwners.get(surface.description.id);
883
+ if (owner && (owner.client !== client || owner.sessionId !== command.sessionId)) {
884
+ sendError(client, command, { code: -32e3, message: "Screencast belongs to another session" });
885
+ return;
886
+ }
887
+ if (command.method === "Page.startScreencast") {
888
+ const claim = { client, sessionId: command.sessionId };
889
+ screencastOwners.set(surface.description.id, claim);
890
+ settleCapture = (accepted) => {
891
+ if (accepted || screencastOwners.get(surface.description.id) !== claim)
892
+ return;
893
+ screencastOwners.delete(surface.description.id);
894
+ json(surface.ws, { type: "command", relayId: `cleanup-${++relaySequence}`, message: { id: 0, method: "Page.stopScreencast" } });
895
+ };
896
+ }
897
+ if (command.method === "Page.stopScreencast")
898
+ settleCapture = (accepted) => {
899
+ if (accepted && screencastOwners.get(surface.description.id) === owner)
900
+ screencastOwners.delete(surface.description.id);
901
+ };
902
+ }
903
+ const relayId = `r${++relaySequence}`;
904
+ const timer = setTimeout(() => {
905
+ pending.delete(relayId);
906
+ settleSubscription?.(false);
907
+ settleCapture?.(false);
908
+ sendError(client, command, { code: -32e3, message: "Surface command timed out" });
909
+ }, options.commandTimeoutMs ?? 3e4);
910
+ pending.set(relayId, { client, command, surface, timer, settleSubscription, settleCapture });
911
+ const payload = {
912
+ message: { id: command.id, method: command.method, params: command.params, sessionId: `${client.id}:${key}` },
913
+ relayId,
914
+ type: "command"
915
+ };
916
+ json(surface.ws, payload);
917
+ };
918
+ const detach = (client, sessionId) => {
919
+ pages?.disconnect(`${client.id}:${sessionId}`);
920
+ void contexts?.disconnect(`${client.id}:${sessionId}`).catch(() => {
921
+ });
922
+ const targetId = client.sessions.get(sessionId);
923
+ const parent = client.parents.get(sessionId) ?? "";
924
+ for (const [child, owner] of [...client.parents])
925
+ if (owner === sessionId)
926
+ detach(client, child);
927
+ client.parents.delete(sessionId);
928
+ client.automaticSessions.delete(sessionId);
929
+ client.autoAttach.delete(sessionId);
930
+ client.discover.delete(sessionId);
931
+ client.sessions.delete(sessionId);
932
+ client.subscriptions.delete(sessionId);
933
+ client.subscriptionChanges.delete(sessionId);
934
+ client.browserSessions.delete(sessionId);
935
+ const surface = targetId ? surfaces.get(targetId) : void 0;
936
+ if (surface) {
937
+ json(surface.ws, { type: "session-closed", sessionId: `${client.id}:${sessionId}` });
938
+ const owner = screencastOwners.get(targetId);
939
+ if (owner?.client === client && owner.sessionId === sessionId) {
940
+ screencastOwners.delete(targetId);
941
+ json(surface.ws, { type: "command", relayId: `cleanup-${++relaySequence}`, message: { id: 0, method: "Page.stopScreencast" } });
942
+ }
943
+ }
944
+ for (const [relayId, item] of pending) {
945
+ if (item.client !== client || item.command.sessionId !== sessionId)
946
+ continue;
947
+ clearTimeout(item.timer);
948
+ pending.delete(relayId);
949
+ sendError(client, item.command, { code: -32001, message: "Session detached" });
950
+ }
951
+ json(client.ws, { ...parent ? { sessionId: parent } : {}, method: "Target.detachedFromTarget", params: { sessionId, ...targetId ? { targetId } : {} } });
952
+ };
953
+ const contextSurfaces = (browserContextId) => {
954
+ const key = typeof browserContextId === "string" ? browserContextId : "default";
955
+ if (key !== "default" && !contexts?.ids().includes(key))
956
+ return void 0;
957
+ return [...surfaces.values()].filter((surface) => (surface.contextId ?? "default") === key);
958
+ };
959
+ const fanOut = (client, command, targets, params = command.params) => Promise.all(targets.map((surface) => new Promise((resolve) => {
960
+ const relayId = `fan-${++relaySequence}`;
961
+ const timer = setTimeout(() => {
962
+ pending.delete(relayId);
963
+ resolve({ id: command.id, error: { code: -32e3, message: "Surface command timed out" } });
964
+ }, options.commandTimeoutMs ?? 3e4);
965
+ pending.set(relayId, { client, command, surface, timer, resolve });
966
+ json(surface.ws, { type: "command", relayId, message: { id: command.id, method: command.method, params } });
967
+ })));
968
+ const firstError = (responses) => {
969
+ const failed = responses.find((response) => response.error);
970
+ if (failed)
971
+ throw new Error(failed.error.message);
972
+ };
973
+ const handleBrowserCommand = (client, command) => {
974
+ const params = command.params ?? {};
975
+ const toPage = (surface) => {
976
+ if (command.method === "Target.getTargetInfo" && typeof params.targetId !== "string")
977
+ sendResponse(client, command, { targetInfo: pageInfo(surface) });
978
+ else
979
+ forward(client, command, surface);
980
+ };
981
+ if (client.directSurfaceId) {
982
+ const surface = surfaces.get(client.directSurfaceId);
983
+ if (!surface)
984
+ return sendError(client, command, { code: -32e3, message: "Target closed" });
985
+ toPage(surface);
986
+ return;
987
+ }
988
+ if (command.sessionId && !client.browserSessions.has(command.sessionId)) {
989
+ const targetId = client.sessions.get(command.sessionId);
990
+ const surface = targetId ? surfaces.get(targetId) : void 0;
991
+ if (!surface)
992
+ return sendError(client, command, { code: -32001, message: "No session with given id" });
993
+ if (command.method === "Target.setAutoAttach") {
994
+ const parent = command.sessionId;
995
+ if (params.autoAttach === true) {
996
+ client.autoAttach.add(parent);
997
+ for (const worker of surfaces.values()) {
998
+ if (!autoAttaches(client, parent, worker))
999
+ continue;
1000
+ if (![...client.sessions].some(([id, target]) => target === worker.description.id && client.parents.get(id) === parent)) {
1001
+ client.automaticSessions.add(attach(client, worker, parent));
1002
+ }
1003
+ }
1004
+ } else {
1005
+ client.autoAttach.delete(parent);
1006
+ for (const id of [...client.automaticSessions])
1007
+ if (client.parents.get(id) === parent)
1008
+ detach(client, id);
1009
+ }
1010
+ sendResponse(client, command);
1011
+ return;
1012
+ }
1013
+ toPage(surface);
1014
+ return;
1015
+ }
1016
+ const hostReply = (operation) => {
1017
+ void operation.then((result) => sendResponse(client, command, result), (error) => sendError(client, command, {
1018
+ code: -32e3,
1019
+ message: error instanceof Error ? error.message : String(error)
1020
+ }));
1021
+ };
1022
+ switch (command.method) {
1023
+ case "Target.createTarget":
1024
+ if (!pages)
1025
+ sendError(client, command);
1026
+ else
1027
+ hostReply(pages.create(`${client.id}:${command.sessionId ?? ""}`, params).then((targetId) => ({ targetId })));
1028
+ break;
1029
+ case "Target.closeTarget":
1030
+ if (!pages)
1031
+ sendError(client, command);
1032
+ else if (typeof params.targetId !== "string")
1033
+ sendError(client, command, { code: -32602, message: "targetId must be a string" });
1034
+ else
1035
+ hostReply(pages.closeTarget(params.targetId).then(() => ({ success: true })));
1036
+ break;
1037
+ case "Target.getBrowserContexts":
1038
+ sendResponse(client, command, { browserContextIds: contexts?.ids() ?? [] });
1039
+ break;
1040
+ case "Target.createBrowserContext":
1041
+ if (!contexts)
1042
+ sendError(client, command);
1043
+ else
1044
+ hostReply(contexts.create(`${client.id}:${command.sessionId ?? ""}`, params).then((browserContextId) => ({ browserContextId })));
1045
+ break;
1046
+ case "Target.disposeBrowserContext":
1047
+ if (!contexts)
1048
+ sendError(client, command);
1049
+ else if (typeof params.browserContextId !== "string")
1050
+ sendError(client, command, { code: -32602, message: "browserContextId must be a string" });
1051
+ else
1052
+ hostReply(contexts.dispose(params.browserContextId).then(() => ({})));
1053
+ break;
1054
+ case "Target.attachToBrowserTarget": {
1055
+ const sessionId = globalThis.crypto.randomUUID().replaceAll("-", "");
1056
+ client.browserSessions.add(sessionId);
1057
+ client.parents.set(sessionId, command.sessionId ?? "");
1058
+ sendResponse(client, command, { sessionId });
1059
+ break;
1060
+ }
1061
+ case "Browser.getVersion":
1062
+ sendResponse(client, command, {
1063
+ jsVersion: "0",
1064
+ // The browser the pages run in, when the dispatcher runs in one.
1065
+ product: `Chrome/${/Chrome\/([\d.]+)/u.exec(globalThis.navigator?.userAgent ?? "")?.[1] ?? "136.0.0.0"}`,
1066
+ protocolVersion: "1.3",
1067
+ revision: "@almostcdp",
1068
+ userAgent: globalThis.navigator?.userAgent ?? "Mozilla/5.0 AlmostCDP/0.1.0"
1069
+ });
1070
+ break;
1071
+ case "Browser.getBrowserCommandLine":
1072
+ sendResponse(client, command, { arguments: [] });
1073
+ break;
1074
+ case "Browser.grantPermissions":
1075
+ case "Browser.resetPermissions": {
1076
+ const key = typeof params.browserContextId === "string" ? params.browserContextId : "default";
1077
+ const targets = contextSurfaces(params.browserContextId);
1078
+ if (!targets) {
1079
+ sendError(client, command, { code: -32e3, message: "Failed to find browser context for id" });
1080
+ break;
1081
+ }
1082
+ if (command.method === "Browser.resetPermissions")
1083
+ contextGrants.delete(key);
1084
+ else
1085
+ contextGrants.set(key, [...contextGrants.get(key) ?? [], params]);
1086
+ hostReply(fanOut(client, command, targets).then((responses) => {
1087
+ firstError(responses);
1088
+ return {};
1089
+ }));
1090
+ break;
1091
+ }
1092
+ case "Storage.getCookies":
1093
+ case "Storage.setCookies":
1094
+ case "Storage.clearCookies": {
1095
+ const targets = contextSurfaces(params.browserContextId);
1096
+ if (!targets) {
1097
+ sendError(client, command, { code: -32e3, message: "Failed to find browser context for id" });
1098
+ break;
1099
+ }
1100
+ const cookies = Array.isArray(params.cookies) ? params.cookies : [];
1101
+ const refusal = !targets.length ? "no surface of the context is open to reach its cookies" : cookies.some((cookie) => cookie.httpOnly === true) ? "HttpOnly cookies are invisible to scripts and cannot be set from a DOM surface" : void 0;
1102
+ if (refusal) {
1103
+ sendError(client, command, { code: -32e3, message: `${command.method} is unavailable on a DOM surface: ${refusal}` });
1104
+ break;
1105
+ }
1106
+ const setting = command.method === "Storage.setCookies";
1107
+ hostReply(fanOut(client, command, targets, setting ? { ...params, [FAN_OUT_PARAM]: true } : params).then((responses) => {
1108
+ firstError(responses);
1109
+ if (command.method === "Storage.getCookies") {
1110
+ const merged = /* @__PURE__ */ new Map();
1111
+ for (const response of responses) {
1112
+ for (const cookie of response.result?.cookies ?? []) {
1113
+ merged.set(`${String(cookie.name)}\0${String(cookie.domain)}\0${String(cookie.path)}`, cookie);
1114
+ }
1115
+ }
1116
+ return { cookies: [...merged.values()] };
1117
+ }
1118
+ if (setting) {
1119
+ const applied = new Set(responses.flatMap((response) => response.result?.applied ?? []));
1120
+ const missing = cookies.filter((_, index) => !applied.has(index)).map((cookie) => String(cookie.domain ?? cookie.url ?? ""));
1121
+ if (missing.length)
1122
+ throw new Error(`Storage.setCookies is unavailable on a DOM surface: no open surface of the context serves ${missing.join(", ")}`);
1123
+ }
1124
+ return {};
1125
+ }));
1126
+ break;
1127
+ }
1128
+ case "Browser.setDownloadBehavior": {
1129
+ if (!options.downloadHost) {
1130
+ sendError(client, command, { code: -32e3, message: "Download policy requires a host download adapter; a live DOM surface cannot enforce it" });
1131
+ break;
1132
+ }
1133
+ const behavior = String(params.behavior ?? "");
1134
+ if (!["default", "deny", "allow", "allowAndName"].includes(behavior)) {
1135
+ sendError(client, command, { code: -32602, message: `Invalid download behavior: ${behavior}` });
1136
+ break;
1137
+ }
1138
+ const downloadPath = typeof params.downloadPath === "string" && params.downloadPath ? params.downloadPath : void 0;
1139
+ if ((behavior === "allow" || behavior === "allowAndName") && !downloadPath) {
1140
+ sendError(client, command, { code: -32602, message: "downloadPath is required for allow and allowAndName" });
1141
+ break;
1142
+ }
1143
+ const targets = contextSurfaces(params.browserContextId);
1144
+ if (!targets) {
1145
+ sendError(client, command, { code: -32e3, message: "Failed to find browser context for id" });
1146
+ break;
1147
+ }
1148
+ const key = typeof params.browserContextId === "string" ? params.browserContextId : "default";
1149
+ const listeners = (downloadPolicies.get(key)?.listeners ?? []).filter((listener) => listener.client !== client || listener.sessionId !== command.sessionId);
1150
+ if (params.eventsEnabled === true)
1151
+ listeners.push({ client, sessionId: command.sessionId });
1152
+ const policy = { behavior, downloadPath, listeners };
1153
+ downloadPolicies.set(key, policy);
1154
+ hostReply(fanOut(client, command, targets, policyCommand(policy).params).then((responses) => {
1155
+ firstError(responses);
1156
+ return {};
1157
+ }));
1158
+ break;
1159
+ }
1160
+ case "Browser.cancelDownload": {
1161
+ if (!options.downloadHost) {
1162
+ sendError(client, command, unavailableCommand(command.method));
1163
+ break;
1164
+ }
1165
+ const guid = String(params.guid ?? "");
1166
+ const surface = surfaces.get(downloadsInFlight.get(guid) ?? "");
1167
+ if (!surface) {
1168
+ sendError(client, command, { code: -32e3, message: `Browser.cancelDownload: no in-flight download with guid ${guid}` });
1169
+ break;
1170
+ }
1171
+ hostReply(fanOut(client, command, [surface], { guid }).then((responses) => {
1172
+ firstError(responses);
1173
+ return {};
1174
+ }));
1175
+ break;
1176
+ }
1177
+ case "Target.getTargets":
1178
+ sendResponse(client, command, { targetInfos: [...surfaces.values()].map(targetInfo) });
1179
+ break;
1180
+ case "Target.getTargetInfo": {
1181
+ const id = typeof params.targetId === "string" ? params.targetId : browserId;
1182
+ if (id === browserId) {
1183
+ sendResponse(client, command, { targetInfo: { targetId: browserId, type: "browser", title: "", url: "", attached: true, canAccessOpener: false } });
1184
+ break;
1185
+ }
1186
+ const surface = id ? surfaces.get(id) : void 0;
1187
+ if (!surface)
1188
+ sendError(client, command, { code: -32e3, message: "No target with given id" });
1189
+ else
1190
+ sendResponse(client, command, { targetInfo: pageInfo(surface) });
1191
+ break;
1192
+ }
1193
+ case "Target.setDiscoverTargets": {
1194
+ const parent = command.sessionId ?? "";
1195
+ const wasDiscovering = client.discover.has(parent);
1196
+ if (params.discover === true)
1197
+ client.discover.add(parent);
1198
+ else
1199
+ client.discover.delete(parent);
1200
+ sendResponse(client, command);
1201
+ if (params.discover === true && !wasDiscovering) {
1202
+ for (const surface of surfaces.values()) {
1203
+ json(client.ws, { ...parent ? { sessionId: parent } : {}, method: "Target.targetCreated", params: { targetInfo: pageInfo(surface) } });
1204
+ }
1205
+ }
1206
+ break;
1207
+ }
1208
+ case "Target.setAutoAttach": {
1209
+ const parent = command.sessionId ?? "";
1210
+ if (params.autoAttach === true)
1211
+ client.autoAttach.add(parent);
1212
+ else
1213
+ client.autoAttach.delete(parent);
1214
+ if (params.autoAttach === true) {
1215
+ for (const surface of surfaces.values()) {
1216
+ if (!autoAttaches(client, parent, surface))
1217
+ continue;
1218
+ if (![...client.sessions].some(([id, target]) => target === surface.description.id && client.parents.get(id) === parent)) {
1219
+ client.automaticSessions.add(attach(client, surface, command.sessionId));
1220
+ }
1221
+ }
1222
+ } else {
1223
+ for (const id of [...client.automaticSessions])
1224
+ if (client.parents.get(id) === parent)
1225
+ detach(client, id);
1226
+ }
1227
+ sendResponse(client, command);
1228
+ break;
1229
+ }
1230
+ case "Target.attachToTarget": {
1231
+ const id = String(params.targetId ?? "");
1232
+ const surface = surfaces.get(id);
1233
+ if (!surface)
1234
+ sendError(client, command, { code: -32e3, message: "No target with given id" });
1235
+ else {
1236
+ const sessionId = attach(client, surface, command.sessionId);
1237
+ sendResponse(client, command, { sessionId });
1238
+ }
1239
+ break;
1240
+ }
1241
+ case "Target.activateTarget": {
1242
+ const surface = surfaces.get(String(params.targetId ?? ""));
1243
+ if (!surface)
1244
+ sendError(client, command, { code: -32e3, message: "No target with given id" });
1245
+ else
1246
+ forward(client, command, surface);
1247
+ break;
1248
+ }
1249
+ case "Target.detachFromTarget": {
1250
+ const sessionId = String(params.sessionId ?? "");
1251
+ const targetId = client.sessions.get(sessionId);
1252
+ if (!targetId && !client.browserSessions.has(sessionId) || client.parents.get(sessionId) !== (command.sessionId ?? "")) {
1253
+ sendError(client, command, { code: -32001, message: "No session with given id" });
1254
+ break;
1255
+ }
1256
+ detach(client, sessionId);
1257
+ sendResponse(client, command);
1258
+ break;
1259
+ }
1260
+ default:
1261
+ sendError(client, command, unavailableCommand(command.method) ?? unsupported(command.method));
1262
+ }
1263
+ };
1264
+ const failPending = (surface, message) => {
1265
+ for (const [relayId, item] of pending) {
1266
+ if (item.surface !== surface)
1267
+ continue;
1268
+ clearTimeout(item.timer);
1269
+ pending.delete(relayId);
1270
+ if (item.resolve)
1271
+ item.resolve({ id: item.command.id, error: { code: -32e3, message } });
1272
+ else
1273
+ sendError(item.client, item.command, { code: -32e3, message });
1274
+ }
1275
+ };
1276
+ const destroySurface = (id) => {
1277
+ const surface = surfaces.get(id);
1278
+ if (!surface)
1279
+ return;
1280
+ const succession = successions.get(id);
1281
+ if (succession?.suspended)
1282
+ clearTimeout(succession.suspended.timer);
1283
+ successions.delete(id);
1284
+ surfaces.delete(id);
1285
+ screencastOwners.delete(id);
1286
+ failPending(surface, "Target closed");
1287
+ for (const viewer of castViewers.get(id) ?? [])
1288
+ viewer.close(1001, "Target closed");
1289
+ for (const client of clients) {
1290
+ if (client.directSurfaceId === id) {
1291
+ client.subscriptions.clear();
1292
+ client.subscriptionChanges.clear();
1293
+ client.ws.close(1001, "Target closed");
1294
+ continue;
1295
+ }
1296
+ for (const [sessionId, targetId] of [...client.sessions]) {
1297
+ if (targetId === id)
1298
+ detach(client, sessionId);
1299
+ }
1300
+ for (const parent of client.discover) {
1301
+ json(client.ws, { ...parent ? { sessionId: parent } : {}, method: "Target.targetDestroyed", params: { targetId: id } });
1302
+ }
1303
+ }
1304
+ };
1305
+ const suspendSurface = (id, succession) => {
1306
+ const surface = surfaces.get(id);
1307
+ const buffer = [];
1308
+ surface.ws = {
1309
+ readyState: 1,
1310
+ send: (data) => {
1311
+ buffer.push(data);
1312
+ },
1313
+ close: () => destroySurface(id),
1314
+ on: () => void 0
1315
+ };
1316
+ failPending(surface, "Inspected target navigated or closed");
1317
+ for (const viewer of castViewers.get(id) ?? [])
1318
+ viewer.close(1012, "Document replaced; reconnect");
1319
+ succession.suspended = { buffer, timer: setTimeout(() => destroySurface(id), successionGraceMs) };
1320
+ };
1321
+ const connect = (ws, path, access = "control") => {
1322
+ const requestUrl = new URL(path, "http://localhost");
1323
+ if (access === "view" && !requestUrl.pathname.startsWith("/casts/")) {
1324
+ ws.close(1008, "View-only access permits only casting");
1325
+ return;
1326
+ }
1327
+ if (requestUrl.pathname === "/__almostcdp/surface") {
1328
+ if (requestUrl.searchParams.get("token") !== surfaceToken) {
1329
+ ws.close(1008, "invalid surface token");
1330
+ return;
1331
+ }
1332
+ let registeredId;
1333
+ const subsurfaces = /* @__PURE__ */ new Map();
1334
+ ws.on("close", () => {
1335
+ for (const peer of [...subsurfaces.values()])
1336
+ peer.closed();
1337
+ });
1338
+ ws.on("message", (bytes) => {
1339
+ let message;
1340
+ try {
1341
+ message = JSON.parse(bytes.toString());
1342
+ if (!message || typeof message !== "object")
1343
+ throw new Error("Invalid message");
1344
+ } catch {
1345
+ ws.close(1003, "invalid JSON");
1346
+ return;
1347
+ }
1348
+ if (message.type === "hello") {
1349
+ if (!message.surface || typeof message.surface.title !== "string" || typeof message.surface.url !== "string" || message.surface.id !== void 0 && (typeof message.surface.id !== "string" || !/^[\w-]+$/.test(message.surface.id))) {
1350
+ ws.close(1008, "invalid surface description");
1351
+ return;
1352
+ }
1353
+ const id = message.surface.id ?? registeredId ?? globalThis.crypto.randomUUID().replaceAll("-", "");
1354
+ const token = message.succession?.token;
1355
+ if (message.succession !== void 0 && (typeof token !== "string" || token.length < 16)) {
1356
+ ws.close(1008, "invalid succession token");
1357
+ return;
1358
+ }
1359
+ const succession = successions.get(id);
1360
+ if (!registeredId && succession && surfaces.has(id) && surfaces.get(id).ws !== ws) {
1361
+ if (token !== succession.token) {
1362
+ ws.close(1008, "surface identity already registered");
1363
+ return;
1364
+ }
1365
+ if (!succession.suspended) {
1366
+ const predecessor = surfaces.get(id).ws;
1367
+ suspendSurface(id, succession);
1368
+ predecessor.close(1e3, "Document replaced by its successor");
1369
+ }
1370
+ const surface2 = surfaces.get(id);
1371
+ clearTimeout(succession.suspended.timer);
1372
+ const queued = succession.suspended.buffer;
1373
+ succession.suspended = void 0;
1374
+ registeredId = id;
1375
+ surface2.ws = ws;
1376
+ surface2.description.title = message.surface.title;
1377
+ surface2.description.url = message.surface.url;
1378
+ json(ws, {
1379
+ type: "resume",
1380
+ state: succession.state,
1381
+ ...succession.loaderId ? { loaderId: succession.loaderId, navigationType: succession.navigationType } : {}
1382
+ });
1383
+ succession.loaderId = void 0;
1384
+ succession.navigationType = void 0;
1385
+ for (const data of queued)
1386
+ if (ws.readyState === 1)
1387
+ ws.send(data);
1388
+ for (const client2 of clients) {
1389
+ const observers = new Set(client2.discover);
1390
+ for (const [sessionId, targetId] of client2.sessions)
1391
+ if (targetId === id)
1392
+ observers.add(client2.parents.get(sessionId) ?? "");
1393
+ for (const parent of observers) {
1394
+ json(client2.ws, { ...parent ? { sessionId: parent } : {}, method: "Target.targetInfoChanged", params: { targetInfo: targetInfo(surface2) } });
1395
+ }
1396
+ }
1397
+ return;
1398
+ }
1399
+ if (registeredId && registeredId !== id || surfaces.has(id) && surfaces.get(id).ws !== ws) {
1400
+ ws.close(1008, "surface identity already registered");
1401
+ return;
1402
+ }
1403
+ const parentId = subsurfaceParents.get(ws);
1404
+ if (pages && parentId === void 0 && !pages.acceptsSurface(id)) {
1405
+ ws.close(1008, "Page allocation is no longer active");
1406
+ return;
1407
+ }
1408
+ if (token !== void 0 && !registeredId) {
1409
+ successions.set(id, { token, state: null });
1410
+ json(ws, { type: "resume", state: null });
1411
+ }
1412
+ const existed = surfaces.has(id);
1413
+ registeredId = id;
1414
+ const surface = {
1415
+ contextId: parentId !== void 0 ? surfaces.get(parentId)?.contextId : pages?.context(id),
1416
+ ...parentId !== void 0 ? { parentId } : {},
1417
+ description: {
1418
+ id,
1419
+ title: message.surface.title,
1420
+ type: message.surface.type ?? "page",
1421
+ url: message.surface.url
1422
+ },
1423
+ ws
1424
+ };
1425
+ surfaces.set(id, surface);
1426
+ const openerMark = pages?.openerMark(id);
1427
+ const ownMark = pages?.ownMark(id);
1428
+ if (openerMark || ownMark)
1429
+ json(ws, { type: "opener", ...openerMark ? { mark: openerMark } : {}, ...ownMark ? { own: ownMark } : {} });
1430
+ for (const client2 of clients) {
1431
+ const observers = new Set(client2.discover);
1432
+ if (existed) {
1433
+ for (const [sessionId, targetId] of client2.sessions)
1434
+ if (targetId === id)
1435
+ observers.add(client2.parents.get(sessionId) ?? "");
1436
+ }
1437
+ for (const parent of observers) {
1438
+ json(client2.ws, { ...parent ? { sessionId: parent } : {}, method: existed ? "Target.targetInfoChanged" : "Target.targetCreated", params: { targetInfo: pageInfo(surface) } });
1439
+ }
1440
+ if (!existed) {
1441
+ for (const parent of client2.autoAttach)
1442
+ if (autoAttaches(client2, parent, surface))
1443
+ client2.automaticSessions.add(attach(client2, surface, parent || void 0));
1444
+ }
1445
+ }
1446
+ if (!existed) {
1447
+ const policy = downloadPolicies.get(surface.contextId ?? "default");
1448
+ if (policy && policy.behavior !== "default") {
1449
+ json(ws, { type: "command", relayId: `cleanup-${++relaySequence}`, message: policyCommand(policy) });
1450
+ }
1451
+ for (const grant of contextGrants.get(surface.contextId ?? "default") ?? []) {
1452
+ json(ws, { type: "command", relayId: `cleanup-${++relaySequence}`, message: { id: 0, method: "Browser.grantPermissions", params: grant } });
1453
+ }
1454
+ }
1455
+ return;
1456
+ }
1457
+ if (message.type === "succession-state") {
1458
+ const succession = registeredId ? successions.get(registeredId) : void 0;
1459
+ if (!succession || surfaces.get(registeredId)?.ws !== ws)
1460
+ return;
1461
+ if (!message.state || typeof message.state !== "object" || Array.isArray(message.state)) {
1462
+ ws.close(1008, "invalid succession state");
1463
+ return;
1464
+ }
1465
+ succession.state = message.state;
1466
+ return;
1467
+ }
1468
+ if (message.type === "response") {
1469
+ const item = pending.get(message.relayId);
1470
+ if (!item || item.surface.ws !== ws)
1471
+ return;
1472
+ if (!message.message || typeof message.message !== "object") {
1473
+ ws.close(1008, "invalid response");
1474
+ return;
1475
+ }
1476
+ pending.delete(message.relayId);
1477
+ clearTimeout(item.timer);
1478
+ const navigated = successions.get(item.surface.description.id);
1479
+ if (navigated && !message.message.error && (item.command.method === "Page.navigate" || item.command.method === "Page.reload")) {
1480
+ const loaderId = message.message.result?.loaderId;
1481
+ navigated.loaderId = typeof loaderId === "string" ? loaderId : void 0;
1482
+ navigated.navigationType = item.command.method === "Page.reload" ? "reload" : "typed";
1483
+ }
1484
+ if (item.resolve) {
1485
+ item.resolve(message.message);
1486
+ return;
1487
+ }
1488
+ item.settleSubscription?.(!message.message.error);
1489
+ item.settleCapture?.(!message.message.error);
1490
+ const response = { ...message.message, id: item.command.id };
1491
+ if (item.command.sessionId)
1492
+ response.sessionId = item.command.sessionId;
1493
+ json(item.client.ws, response);
1494
+ return;
1495
+ }
1496
+ if (message.type === "cast" && registeredId) {
1497
+ for (const viewer of castViewers.get(registeredId) ?? [])
1498
+ json(viewer, message.event);
1499
+ return;
1500
+ }
1501
+ if (message.type === "sub" && registeredId && typeof message.sub === "string") {
1502
+ const pageId = registeredId;
1503
+ let peer = subsurfaces.get(message.sub);
1504
+ if (message.open === true && !peer) {
1505
+ const sub = message.sub;
1506
+ peer = new SubsurfacePeer((frame2) => json(ws, { type: "sub", sub, ...frame2 }));
1507
+ subsurfaces.set(sub, peer);
1508
+ subsurfaceParents.set(peer, pageId);
1509
+ peer.on("close", () => subsurfaces.delete(sub));
1510
+ connect(peer, `/__almostcdp/surface?token=${encodeURIComponent(surfaceToken)}`);
1511
+ }
1512
+ if (typeof message.data === "string")
1513
+ peer?.received(message.data);
1514
+ if (message.close === true)
1515
+ peer?.closed();
1516
+ return;
1517
+ }
1518
+ if (message.type === "close-window" && registeredId) {
1519
+ const closing = registeredId;
1520
+ void pages?.closeTarget(closing).catch((error) => console.warn(`AlmostCDP: window.close() closed no page: ${error instanceof Error ? error.message : String(error)}`));
1521
+ return;
1522
+ }
1523
+ if (message.type === "open-window" && registeredId) {
1524
+ const openerId = registeredId;
1525
+ if (!pages || typeof message.url !== "string")
1526
+ return;
1527
+ const owner = pages.owner(openerId) ?? `popup:${openerId}`;
1528
+ const contextId = surfaces.get(openerId)?.contextId;
1529
+ const mark = typeof message.opener === "string" && /^[\w-]{16,}$/u.test(message.opener) ? message.opener : void 0;
1530
+ const own = typeof message.mark === "string" && /^[\w-]{16,}$/u.test(message.mark) ? message.mark : void 0;
1531
+ void pages.create(owner, { url: message.url, ...contextId && contextId !== "default" ? { browserContextId: contextId } : {} }, openerId, mark, own).catch((error) => console.warn(`AlmostCDP: window.open(${JSON.stringify(message.url)}) opened no page: ${error instanceof Error ? error.message : String(error)}`));
1532
+ return;
1533
+ }
1534
+ if (message.type === "download" && registeredId) {
1535
+ const surfaceId = registeredId;
1536
+ const contextId = surfaces.get(surfaceId)?.contextId;
1537
+ const policy = downloadPolicies.get(contextId ?? "default");
1538
+ const reply = (written) => json(ws, { type: "download-written", ...written });
1539
+ if (!options.downloadHost || !policy?.downloadPath || policy.behavior !== "allow" && policy.behavior !== "allowAndName" || typeof message.guid !== "string" || typeof message.data !== "string") {
1540
+ reply({ error: "downloads are not accepted for this context", guid: String(message.guid) });
1541
+ return;
1542
+ }
1543
+ const name = policy.behavior === "allowAndName" ? message.guid : String(message.suggestedFilename ?? "").replace(/[\\/]/g, "_") || message.guid;
1544
+ const filePath = `${policy.downloadPath.replace(/[\\/]+$/, "")}/${name}`;
1545
+ void options.downloadHost.write(contextId, filePath, base64ToBytes(message.data)).then(() => reply({ filePath, guid: message.guid }), (error) => reply({ error: error instanceof Error ? error.message : String(error), guid: message.guid }));
1546
+ return;
1547
+ }
1548
+ if (message.type === "event" && registeredId) {
1549
+ if (!message.message || typeof message.message.method !== "string") {
1550
+ ws.close(1008, "invalid event");
1551
+ return;
1552
+ }
1553
+ if (message.message.method.startsWith("Browser.download")) {
1554
+ const params = message.message.params ?? {};
1555
+ const guid = String(params.guid ?? "");
1556
+ if (message.message.method === "Browser.downloadWillBegin")
1557
+ downloadsInFlight.set(guid, registeredId);
1558
+ else if (params.state === "completed" || params.state === "canceled")
1559
+ downloadsInFlight.delete(guid);
1560
+ const policy = downloadPolicies.get(surfaces.get(registeredId)?.contextId ?? "default");
1561
+ for (const listener of policy?.listeners ?? []) {
1562
+ json(listener.client.ws, { method: message.message.method, params, ...listener.sessionId ? { sessionId: listener.sessionId } : {} });
1563
+ }
1564
+ return;
1565
+ }
1566
+ if (message.message.method === "Page.screencastFrame") {
1567
+ const owner = screencastOwners.get(registeredId);
1568
+ const { sessionId: audience2, ...event2 } = message.message;
1569
+ if (owner && audience2 === `${owner.client.id}:${owner.sessionId ?? "direct"}`)
1570
+ json(owner.client.ws, { ...event2, ...owner.sessionId ? { sessionId: owner.sessionId } : {} });
1571
+ return;
1572
+ }
1573
+ const { sessionId: audience, ...event } = message.message;
1574
+ const domain = event.method.split(".")[0];
1575
+ const requiresSubscription = ["Runtime", "Network", "Log", "DOM", "Page", "Accessibility"].includes(domain);
1576
+ const subscribed = (client2, key) => (!audience || audience === `${client2.id}:${key}`) && (!requiresSubscription || client2.subscriptions.get(key)?.has(domain));
1577
+ for (const client2 of clients) {
1578
+ if (client2.directSurfaceId === registeredId && subscribed(client2, "direct")) {
1579
+ json(client2.ws, event);
1580
+ continue;
1581
+ }
1582
+ for (const [sessionId, targetId] of client2.sessions) {
1583
+ if (targetId === registeredId && subscribed(client2, sessionId))
1584
+ json(client2.ws, { ...event, sessionId });
1585
+ }
1586
+ }
1587
+ }
1588
+ });
1589
+ ws.on("close", () => {
1590
+ if (!registeredId || surfaces.get(registeredId)?.ws !== ws)
1591
+ return;
1592
+ const succession = successions.get(registeredId);
1593
+ if (succession)
1594
+ suspendSurface(registeredId, succession);
1595
+ else
1596
+ destroySurface(registeredId);
1597
+ });
1598
+ return;
1599
+ }
1600
+ const pageMatch = requestUrl.pathname.match(/^\/devtools\/page\/([^/]+)$/);
1601
+ const browserMatch = requestUrl.pathname.match(/^\/devtools\/browser\/([^/]+)$/);
1602
+ const castMatch = requestUrl.pathname.match(/^\/casts\/([^/]+)$/);
1603
+ if (castMatch) {
1604
+ const surfaceId = castMatch[1];
1605
+ if (!surfaces.has(surfaceId)) {
1606
+ ws.close(1013, "target not available");
1607
+ return;
1608
+ }
1609
+ const viewers = castViewers.get(surfaceId) ?? /* @__PURE__ */ new Set();
1610
+ viewers.add(ws);
1611
+ castViewers.set(surfaceId, viewers);
1612
+ json(surfaces.get(surfaceId).ws, { type: "cast-reset" });
1613
+ ws.on("message", (bytes) => {
1614
+ if (access === "view") {
1615
+ ws.close(1008, "View-only access cannot send input");
1616
+ return;
1617
+ }
1618
+ const surface = surfaces.get(surfaceId);
1619
+ if (!surface)
1620
+ return;
1621
+ let input;
1622
+ try {
1623
+ input = JSON.parse(bytes.toString());
1624
+ } catch {
1625
+ ws.close(1003, "invalid JSON");
1626
+ return;
1627
+ }
1628
+ if (typeof input.method !== "string" || !input.method.startsWith("Input.")) {
1629
+ ws.close(1008, "cast input must be a CDP Input command");
1630
+ return;
1631
+ }
1632
+ const payload = {
1633
+ message: {
1634
+ id: 0,
1635
+ method: input.method,
1636
+ params: typeof input.params === "object" && input.params ? input.params : {}
1637
+ },
1638
+ relayId: `cast-${++relaySequence}`,
1639
+ type: "command"
1640
+ };
1641
+ json(surface.ws, payload);
1642
+ });
1643
+ ws.on("close", () => {
1644
+ viewers.delete(ws);
1645
+ if (!viewers.size)
1646
+ castViewers.delete(surfaceId);
1647
+ });
1648
+ return;
1649
+ }
1650
+ if (!pageMatch && (!browserMatch || browserMatch[1] !== browserId)) {
1651
+ ws.close(1008, "unknown endpoint");
1652
+ return;
1653
+ }
1654
+ if (pageMatch && !surfaces.has(pageMatch[1])) {
1655
+ ws.close(1008, "unknown target");
1656
+ return;
1657
+ }
1658
+ const client = {
1659
+ id: globalThis.crypto.randomUUID(),
1660
+ autoAttach: /* @__PURE__ */ new Set(),
1661
+ automaticSessions: /* @__PURE__ */ new Set(),
1662
+ parents: /* @__PURE__ */ new Map(),
1663
+ browserSessions: /* @__PURE__ */ new Set(),
1664
+ subscriptions: /* @__PURE__ */ new Map(),
1665
+ subscriptionChanges: /* @__PURE__ */ new Map(),
1666
+ directSurfaceId: pageMatch?.[1],
1667
+ discover: /* @__PURE__ */ new Set(),
1668
+ sessions: /* @__PURE__ */ new Map(),
1669
+ ws
1670
+ };
1671
+ clients.add(client);
1672
+ ws.on("message", (bytes) => {
1673
+ let command;
1674
+ try {
1675
+ command = JSON.parse(bytes.toString());
1676
+ if (!command || !Number.isSafeInteger(command.id) || typeof command.method !== "string" || command.params !== void 0 && (!command.params || typeof command.params !== "object" || Array.isArray(command.params)) || command.sessionId !== void 0 && typeof command.sessionId !== "string")
1677
+ throw new Error("Invalid command");
1678
+ } catch {
1679
+ ws.close(1003, "invalid JSON");
1680
+ return;
1681
+ }
1682
+ commandCounts.set(command.method, (commandCounts.get(command.method) ?? 0) + 1);
1683
+ handleBrowserCommand(client, command);
1684
+ });
1685
+ ws.on("close", () => {
1686
+ clients.delete(client);
1687
+ for (const policy of downloadPolicies.values())
1688
+ policy.listeners = policy.listeners.filter((listener) => listener.client !== client);
1689
+ for (const scope of ["", ...client.browserSessions]) {
1690
+ pages?.disconnect(`${client.id}:${scope}`);
1691
+ void contexts?.disconnect(`${client.id}:${scope}`).catch(() => {
1692
+ });
1693
+ }
1694
+ for (const [sessionId, targetId] of client.sessions) {
1695
+ const surface = surfaces.get(targetId);
1696
+ if (surface)
1697
+ json(surface.ws, { type: "session-closed", sessionId: `${client.id}:${sessionId}` });
1698
+ }
1699
+ if (client.directSurfaceId) {
1700
+ const surface = surfaces.get(client.directSurfaceId);
1701
+ if (surface)
1702
+ json(surface.ws, { type: "session-closed", sessionId: `${client.id}:direct` });
1703
+ }
1704
+ for (const [targetId, owner] of screencastOwners) {
1705
+ if (owner.client !== client)
1706
+ continue;
1707
+ screencastOwners.delete(targetId);
1708
+ const surface = surfaces.get(targetId);
1709
+ if (surface)
1710
+ json(surface.ws, { type: "command", relayId: `cleanup-${++relaySequence}`, message: { id: 0, method: "Page.stopScreencast" } });
1711
+ }
1712
+ for (const [relayId, item] of pending) {
1713
+ if (item.client === client) {
1714
+ clearTimeout(item.timer);
1715
+ pending.delete(relayId);
1716
+ }
1717
+ }
1718
+ });
1719
+ };
1720
+ return {
1721
+ connect,
1722
+ targets: () => [...surfaces.values()].map((surface) => ({ ...surface.description })),
1723
+ compatibility: () => ({
1724
+ manifest: SUPPORT_MANIFEST,
1725
+ observedCommands: Object.fromEntries([...commandCounts].sort(([a], [b]) => a.localeCompare(b)))
1726
+ }),
1727
+ async close() {
1728
+ for (const item of pending.values())
1729
+ clearTimeout(item.timer);
1730
+ pending.clear();
1731
+ for (const client of clients)
1732
+ client.ws.close(1001, "server closing");
1733
+ for (const surface of surfaces.values())
1734
+ surface.ws.close(1001, "server closing");
1735
+ for (const viewers of castViewers.values()) {
1736
+ for (const viewer of viewers)
1737
+ viewer.close(1001, "server closing");
1738
+ }
1739
+ const cleanup = await Promise.allSettled([pages?.close(), contexts?.close()]);
1740
+ const errors = cleanup.filter((result) => result.status === "rejected");
1741
+ if (errors.length === 1)
1742
+ throw errors[0].reason;
1743
+ if (errors.length)
1744
+ throw new AggregateError(errors.map((result) => result.reason), "Host cleanup failed");
1745
+ }
1746
+ };
1747
+ }
1748
+
1749
+ // node_modules/@volter/almostcdp/dist/socket-server.js
1750
+ var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
1751
+ var MAX_HEADER_BYTES = 64 * 1024;
1752
+ var encoder2 = new TextEncoder();
1753
+ function createSocketEndpoint(options) {
1754
+ const browserId = globalThis.crypto.randomUUID().replaceAll("-", "");
1755
+ const surfaceToken = randomHex(24);
1756
+ const dispatcher = createDispatcher({ ...options, browserId, surfaceToken });
1757
+ const sockets = /* @__PURE__ */ new Set();
1758
+ let closed = false;
1759
+ const source = { browserId, targets: dispatcher.targets, compatibility: dispatcher.compatibility };
1760
+ const serve = (socket) => {
1761
+ if (closed) {
1762
+ socket.close();
1763
+ return;
1764
+ }
1765
+ sockets.add(socket);
1766
+ socket.onClose(() => sockets.delete(socket));
1767
+ let head = new Uint8Array();
1768
+ let upgraded;
1769
+ let pending;
1770
+ let answered = false;
1771
+ const answer = (status, body, headOnly = false) => {
1772
+ answered = true;
1773
+ head = new Uint8Array();
1774
+ respond(socket, status, body, headOnly);
1775
+ };
1776
+ socket.onData((bytes) => {
1777
+ if (upgraded) {
1778
+ upgraded(bytes);
1779
+ return;
1780
+ }
1781
+ if (pending) {
1782
+ pending.push(bytes);
1783
+ return;
1784
+ }
1785
+ if (answered)
1786
+ return;
1787
+ const from = Math.max(0, head.byteLength - 3);
1788
+ head = concat(head, bytes);
1789
+ const end = indexOfHeaderEnd(head, from);
1790
+ if (end < 0) {
1791
+ if (head.byteLength > MAX_HEADER_BYTES) {
1792
+ console.warn(`CDP endpoint: a connection sent ${head.byteLength} bytes with no HTTP request header; it is answered 431 and closed. It starts ${JSON.stringify(new TextDecoder().decode(head.subarray(0, 32)))}.`);
1793
+ answer(431, { error: "request header too large" });
1794
+ }
1795
+ return;
1796
+ }
1797
+ const request = parseRequest(new TextDecoder().decode(head.subarray(0, end)));
1798
+ const rest = head.subarray(end + 4);
1799
+ if (!request) {
1800
+ answer(400, { error: "malformed request" });
1801
+ return;
1802
+ }
1803
+ const access = options.authorize ? options.authorize(request) : "control";
1804
+ if (!access) {
1805
+ answer(401, { error: "unauthorized" });
1806
+ return;
1807
+ }
1808
+ if ((request.headers.upgrade ?? "").toLowerCase() === "websocket") {
1809
+ const key = request.headers["sec-websocket-key"];
1810
+ if (request.method !== "GET" || !key || request.headers["sec-websocket-version"] !== "13" || !/\bupgrade\b/i.test(request.headers.connection ?? "")) {
1811
+ answer(400, { error: "invalid WebSocket handshake" });
1812
+ return;
1813
+ }
1814
+ pending = rest.byteLength ? [rest] : [];
1815
+ void acceptKey(key).then((accept) => {
1816
+ socket.write(encoder2.encode(`HTTP/1.1 101 Switching Protocols\r
1817
+ Upgrade: websocket\r
1818
+ Connection: Upgrade\r
1819
+ Sec-WebSocket-Accept: ${accept}\r
1820
+ \r
1821
+ `));
1822
+ const peer = new SocketPeer(socket, options.maxMessageBytes ?? 32 * 1024 * 1024);
1823
+ upgraded = (chunk) => peer.receive(chunk);
1824
+ dispatcher.connect(peer, request.path, access);
1825
+ for (const chunk of pending)
1826
+ peer.receive(chunk);
1827
+ pending = void 0;
1828
+ }, () => socket.close());
1829
+ return;
1830
+ }
1831
+ if (access !== "control" || request.method !== "GET" && request.method !== "HEAD") {
1832
+ answer(access !== "control" ? 403 : 405, { error: "discovery is read-only and requires control access" });
1833
+ return;
1834
+ }
1835
+ const { status, body } = discoveryDocument(source, options.address, new URL(request.path, "http://localhost").pathname);
1836
+ answer(status, body, request.method === "HEAD");
1837
+ });
1838
+ };
1839
+ return {
1840
+ browserId,
1841
+ serve,
1842
+ attachSurface: (peer) => dispatcher.connect(peer, `/__almostcdp/surface?token=${surfaceToken}`),
1843
+ attachClient: (peer) => dispatcher.connect(peer, `/devtools/browser/${browserId}`),
1844
+ discovery: (pathname, address = options.address) => discoveryDocument(source, address, pathname),
1845
+ connect: (peer, path, access = "control") => {
1846
+ if (new URL(path, "http://localhost").pathname.startsWith("/__almostcdp/")) {
1847
+ peer.close(1008, "Surface registration is the host's");
1848
+ return;
1849
+ }
1850
+ dispatcher.connect(peer, path, access);
1851
+ },
1852
+ targets: dispatcher.targets,
1853
+ compatibility: dispatcher.compatibility,
1854
+ async close() {
1855
+ closed = true;
1856
+ for (const socket of [...sockets])
1857
+ socket.close();
1858
+ await dispatcher.close();
1859
+ }
1860
+ };
1861
+ }
1862
+ var SocketPeer = class {
1863
+ socket;
1864
+ maxMessageBytes;
1865
+ readyState = 1;
1866
+ buffer = new Uint8Array();
1867
+ fragments = [];
1868
+ fragmentBytes = 0;
1869
+ fragmented = false;
1870
+ messageListeners = [];
1871
+ closeListeners = [];
1872
+ decoder = new TextDecoder("utf-8", { fatal: true });
1873
+ constructor(socket, maxMessageBytes) {
1874
+ this.socket = socket;
1875
+ this.maxMessageBytes = maxMessageBytes;
1876
+ socket.onClose(() => this.finish());
1877
+ }
1878
+ on(event, listener) {
1879
+ if (event === "message")
1880
+ this.messageListeners.push(listener);
1881
+ else
1882
+ this.closeListeners.push(listener);
1883
+ }
1884
+ send(data) {
1885
+ if (this.readyState !== 1)
1886
+ return;
1887
+ this.socket.write(frame(1, encoder2.encode(data)));
1888
+ }
1889
+ close(code = 1e3, reason = "") {
1890
+ if (this.readyState !== 1)
1891
+ return;
1892
+ const reasonBytes = encoder2.encode(reason).subarray(0, 123);
1893
+ const payload = new Uint8Array(2 + reasonBytes.byteLength);
1894
+ new DataView(payload.buffer).setUint16(0, code);
1895
+ payload.set(reasonBytes, 2);
1896
+ this.readyState = 2;
1897
+ this.socket.write(frame(8, payload));
1898
+ this.socket.close();
1899
+ this.finish();
1900
+ }
1901
+ receive(bytes) {
1902
+ if (this.readyState === 3)
1903
+ return;
1904
+ this.buffer = concat(this.buffer, bytes);
1905
+ for (; ; ) {
1906
+ if (this.buffer.byteLength < 2)
1907
+ return;
1908
+ const first = this.buffer[0], second = this.buffer[1];
1909
+ const fin = (first & 128) !== 0, opcode = first & 15;
1910
+ if ((first & 112) !== 0 || (second & 128) === 0) {
1911
+ this.close(1002, "Protocol error");
1912
+ return;
1913
+ }
1914
+ let length = second & 127, offset = 2;
1915
+ if (length === 126) {
1916
+ if (this.buffer.byteLength < 4)
1917
+ return;
1918
+ length = this.buffer[2] << 8 | this.buffer[3];
1919
+ offset = 4;
1920
+ } else if (length === 127) {
1921
+ if (this.buffer.byteLength < 10)
1922
+ return;
1923
+ const view = new DataView(this.buffer.buffer, this.buffer.byteOffset + 2, 8);
1924
+ if (view.getUint32(0) !== 0) {
1925
+ this.close(1009, "Message too big");
1926
+ return;
1927
+ }
1928
+ length = view.getUint32(4);
1929
+ offset = 10;
1930
+ }
1931
+ if (length > this.maxMessageBytes) {
1932
+ this.close(1009, "Message too big");
1933
+ return;
1934
+ }
1935
+ if (this.buffer.byteLength < offset + 4 + length)
1936
+ return;
1937
+ const mask = this.buffer.subarray(offset, offset + 4);
1938
+ const payload = this.buffer.slice(offset + 4, offset + 4 + length);
1939
+ for (let index = 0; index < payload.byteLength; index++)
1940
+ payload[index] ^= mask[index & 3];
1941
+ this.buffer = this.buffer.subarray(offset + 4 + length);
1942
+ if (opcode >= 8) {
1943
+ if (!fin || length > 125) {
1944
+ this.close(1002, "Protocol error");
1945
+ return;
1946
+ }
1947
+ if (opcode === 8) {
1948
+ this.close(1e3, "");
1949
+ return;
1950
+ }
1951
+ if (opcode === 9)
1952
+ this.socket.write(frame(10, payload));
1953
+ else if (opcode !== 10) {
1954
+ this.close(1002, "Protocol error");
1955
+ return;
1956
+ }
1957
+ continue;
1958
+ }
1959
+ if (opcode === 2 || opcode === 0 && !this.fragmented || opcode === 1 && this.fragmented || opcode > 2) {
1960
+ this.close(opcode === 2 ? 1003 : 1002, opcode === 2 ? "CDP messages are text" : "Protocol error");
1961
+ return;
1962
+ }
1963
+ this.fragmentBytes += payload.byteLength;
1964
+ if (this.fragmentBytes > this.maxMessageBytes) {
1965
+ this.close(1009, "Message too big");
1966
+ return;
1967
+ }
1968
+ this.fragments.push(payload);
1969
+ this.fragmented = !fin;
1970
+ if (!fin)
1971
+ continue;
1972
+ const message = this.fragments.length === 1 ? this.fragments[0] : this.fragments.reduce(concat, new Uint8Array());
1973
+ this.fragments = [];
1974
+ this.fragmentBytes = 0;
1975
+ let text;
1976
+ try {
1977
+ text = this.decoder.decode(message);
1978
+ } catch {
1979
+ this.close(1007, "Invalid UTF-8");
1980
+ return;
1981
+ }
1982
+ for (const listener of this.messageListeners)
1983
+ listener(text);
1984
+ if (this.readyState !== 1)
1985
+ return;
1986
+ }
1987
+ }
1988
+ finish() {
1989
+ if (this.readyState === 3)
1990
+ return;
1991
+ this.readyState = 3;
1992
+ for (const listener of this.closeListeners)
1993
+ listener();
1994
+ }
1995
+ };
1996
+ function frame(opcode, payload) {
1997
+ const length = payload.byteLength;
1998
+ const header = length < 126 ? 2 : length < 65536 ? 4 : 10;
1999
+ const out = new Uint8Array(header + length);
2000
+ out[0] = 128 | opcode;
2001
+ if (length < 126)
2002
+ out[1] = length;
2003
+ else if (length < 65536) {
2004
+ out[1] = 126;
2005
+ out[2] = length >> 8;
2006
+ out[3] = length & 255;
2007
+ } else {
2008
+ out[1] = 127;
2009
+ new DataView(out.buffer).setUint32(6, length);
2010
+ }
2011
+ out.set(payload, header);
2012
+ return out;
2013
+ }
2014
+ function respond(socket, status, body, headOnly = false) {
2015
+ const bytes = encoder2.encode(JSON.stringify(body));
2016
+ const reason = { 200: "OK", 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 431: "Request Header Fields Too Large" }[status] ?? "";
2017
+ socket.write(encoder2.encode(`HTTP/1.1 ${status} ${reason}\r
2018
+ Content-Type: application/json; charset=UTF-8\r
2019
+ Content-Length: ${bytes.byteLength}\r
2020
+ Connection: close\r
2021
+ \r
2022
+ `));
2023
+ if (!headOnly)
2024
+ socket.write(bytes);
2025
+ socket.close();
2026
+ }
2027
+ function parseRequest(text) {
2028
+ const [line, ...fields] = text.split("\r\n");
2029
+ const match = /^([A-Z]+) (\/\S*) HTTP\/1\.[01]$/.exec(line ?? "");
2030
+ if (!match)
2031
+ return void 0;
2032
+ const headers = {};
2033
+ for (const field of fields) {
2034
+ const colon = field.indexOf(":");
2035
+ if (colon <= 0)
2036
+ return void 0;
2037
+ headers[field.slice(0, colon).trim().toLowerCase()] = field.slice(colon + 1).trim();
2038
+ }
2039
+ return { method: match[1], path: match[2], headers };
2040
+ }
2041
+ async function acceptKey(key) {
2042
+ const digest = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-1", encoder2.encode(key + GUID)));
2043
+ let binary = "";
2044
+ for (const byte of digest)
2045
+ binary += String.fromCharCode(byte);
2046
+ return btoa(binary);
2047
+ }
2048
+ function indexOfHeaderEnd(bytes, from = 0) {
2049
+ for (let index = Math.max(3, from + 3); index < bytes.byteLength; index++) {
2050
+ if (bytes[index - 3] === 13 && bytes[index - 2] === 10 && bytes[index - 1] === 13 && bytes[index] === 10)
2051
+ return index - 3;
2052
+ }
2053
+ return -1;
2054
+ }
2055
+ function concat(left, right) {
2056
+ if (!left.byteLength)
2057
+ return right;
2058
+ const out = new Uint8Array(left.byteLength + right.byteLength);
2059
+ out.set(left);
2060
+ out.set(right, left.byteLength);
2061
+ return out;
2062
+ }
2063
+ function randomHex(bytes) {
2064
+ return [...globalThis.crypto.getRandomValues(new Uint8Array(bytes))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2065
+ }
2066
+
2067
+ // node_modules/@volter/almostcdp/dist/message-port.js
2068
+ var MessagePortTransport = class extends EventTarget {
2069
+ port;
2070
+ readyState = 1;
2071
+ constructor(port) {
2072
+ super();
2073
+ this.port = port;
2074
+ port.addEventListener("message", this.receive);
2075
+ port.addEventListener("messageerror", this.failed);
2076
+ port.start();
2077
+ }
2078
+ receive = (event) => {
2079
+ const message = event.data;
2080
+ if (message?.type === "close") {
2081
+ this.finish();
2082
+ return;
2083
+ }
2084
+ if (message?.type !== "data" || typeof message.data !== "string") {
2085
+ this.close(1003, "Invalid transport envelope");
2086
+ return;
2087
+ }
2088
+ this.dispatchEvent(new MessageEvent("message", { data: message.data }));
2089
+ };
2090
+ failed = () => {
2091
+ this.close(1003, "MessagePort decode failure");
2092
+ };
2093
+ finish() {
2094
+ if (this.readyState === 3)
2095
+ return;
2096
+ this.readyState = 3;
2097
+ this.port.removeEventListener("message", this.receive);
2098
+ this.port.removeEventListener("messageerror", this.failed);
2099
+ this.port.close();
2100
+ this.dispatchEvent(new Event("close"));
2101
+ }
2102
+ send(data) {
2103
+ if (this.readyState !== 1)
2104
+ throw new Error("MessagePort transport is closed");
2105
+ this.port.postMessage({ type: "data", data });
2106
+ }
2107
+ close(code = 1e3, reason = "Transport closed") {
2108
+ if (this.readyState === 3)
2109
+ return;
2110
+ this.port.postMessage({ type: "close", code, reason });
2111
+ this.finish();
2112
+ }
2113
+ on(event, listener) {
2114
+ this.addEventListener(event, (message) => {
2115
+ if (event === "message")
2116
+ listener(message.data);
2117
+ else
2118
+ listener();
2119
+ });
2120
+ }
2121
+ };
2122
+
2123
+ // packages/runtime/src/sandbox-protocol.ts
2124
+ var SANDBOX_CHANNEL = "supercode-sandbox-v1";
2125
+ function isSandboxEnvelope(value, nonce) {
2126
+ if (!value || typeof value !== "object") return false;
2127
+ const candidate = value;
2128
+ return candidate.channel === SANDBOX_CHANNEL && candidate.nonce === nonce && typeof candidate.type === "string";
2129
+ }
2130
+
2131
+ // packages/runtime/src/browser-base64.ts
2132
+ function decodedBase64ByteLength(value) {
2133
+ if (value.length % 4 !== 0) return;
2134
+ const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
2135
+ const dataLength = value.length - padding;
2136
+ for (let index = 0; index < value.length; index += 1) {
2137
+ const code = value.charCodeAt(index);
2138
+ const data = index < dataLength;
2139
+ const alphabet = code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47;
2140
+ if (data ? !alphabet : code !== 61) return;
2141
+ }
2142
+ return value.length / 4 * 3 - padding;
2143
+ }
2144
+ function encodeBase64(value) {
2145
+ const native = value.toBase64;
2146
+ if (typeof native === "function") return native.call(value);
2147
+ let binary = "";
2148
+ for (let offset = 0; offset < value.byteLength; offset += 32768) {
2149
+ binary += String.fromCharCode(...value.subarray(offset, offset + 32768));
2150
+ }
2151
+ return btoa(binary);
2152
+ }
2153
+
2154
+ // packages/runtime/src/browser-virtual-websocket.ts
2155
+ function virtualFrameByteLength(data) {
2156
+ return "bytes" in data ? data.bytes.byteLength : decodedBase64ByteLength(data.base64);
2157
+ }
2158
+ var OPEN_TIMEOUT_MS = 3e4;
2159
+ var MAX_VIRTUAL_WEBSOCKET_FRAME_BYTES = 32 * 1024 * 1024;
2160
+ var BrowserVirtualWebSocketHub = class {
2161
+ constructor(registeredPort, registry, connector) {
2162
+ this.registeredPort = registeredPort;
2163
+ this.registry = registry;
2164
+ this.connector = connector;
2165
+ }
2166
+ registeredPort;
2167
+ registry;
2168
+ connector;
2169
+ sockets = /* @__PURE__ */ new Map();
2170
+ closingAll = false;
2171
+ open(id, port, path, protocols, handlers, addressed) {
2172
+ if (this.closingAll) throw new Error("Virtual WebSockets are closing.");
2173
+ if (!Number.isSafeInteger(id) || id < 1) throw new Error("Invalid virtual WebSocket identifier.");
2174
+ const closing = this.sockets.get(id);
2175
+ if (closing && closing.state === 2) this.terminate(id, closing, 1001, "");
2176
+ if (this.sockets.has(id)) throw new Error("Invalid virtual WebSocket identifier.");
2177
+ assertPort(port);
2178
+ if (!this.registeredPort(port)) throw new Error("No virtual server is listening on that WebSocket port.");
2179
+ if (!validPath(path)) throw new Error("Invalid virtual WebSocket request target.");
2180
+ if (!Array.isArray(protocols) || protocols.length > 16 || new Set(protocols).size !== protocols.length || protocols.some((protocol) => typeof protocol !== "string" || !validProtocol(protocol))) {
2181
+ throw new Error("Invalid virtual WebSocket protocol list.");
2182
+ }
2183
+ const socket = {
2184
+ port,
2185
+ state: 0,
2186
+ handlers,
2187
+ timeout: setTimeout(() => {
2188
+ if (this.sockets.get(id) !== socket || socket.state !== 0) return;
2189
+ this.terminate(id, socket, 1006, "", "Virtual WebSocket connection timed out.");
2190
+ }, OPEN_TIMEOUT_MS)
2191
+ };
2192
+ this.sockets.set(id, socket);
2193
+ const endpoint = this.registry?.endpoint(port);
2194
+ const bridge = endpoint ? (next) => endpoint.open(path, [...protocols], next) : this.connector ? (next) => this.connector.open(port, path, [...protocols], next, validAuthority(addressed?.host) ? { host: addressed.host } : void 0) : void 0;
2195
+ if (!bridge) {
2196
+ clearTimeout(socket.timeout);
2197
+ this.sockets.delete(id);
2198
+ throw new Error("The virtual WebSocket port has no engine connector.");
2199
+ }
2200
+ try {
2201
+ const connection = bridge({
2202
+ open: (protocol) => {
2203
+ if (this.sockets.get(id) !== socket || socket.state !== 0) return;
2204
+ if (typeof protocol !== "string" || protocol !== "" && !protocols.includes(protocol)) {
2205
+ this.terminate(id, socket, 1006, "", "The virtual server selected an unrequested WebSocket protocol.");
2206
+ return;
2207
+ }
2208
+ clearTimeout(socket.timeout);
2209
+ socket.timeout = void 0;
2210
+ socket.state = 1;
2211
+ this.notify(() => handlers.open(protocol));
2212
+ },
2213
+ message: (data) => {
2214
+ if (this.sockets.get(id) !== socket || socket.state !== 1) return;
2215
+ try {
2216
+ assertFrameSize(data);
2217
+ } catch (cause) {
2218
+ this.terminate(id, socket, 1009, "Invalid server frame", cause instanceof Error ? cause.message : String(cause));
2219
+ return;
2220
+ }
2221
+ this.notify(() => handlers.message(data));
2222
+ },
2223
+ close: (code, reason, clean) => {
2224
+ if (!this.retire(id, socket)) return;
2225
+ this.notify(() => handlers.close(code, reason, clean));
2226
+ },
2227
+ error: (message) => {
2228
+ if (this.sockets.get(id) === socket) this.terminate(id, socket, 1006, "", message);
2229
+ }
2230
+ });
2231
+ socket.connection = connection;
2232
+ if (this.sockets.get(id) !== socket) this.abortConnection(socket);
2233
+ else if (socket.closing) this.requestClose(id, socket);
2234
+ } catch (cause) {
2235
+ this.retire(id, socket);
2236
+ this.abortConnection(socket);
2237
+ throw cause;
2238
+ }
2239
+ }
2240
+ send(id, data) {
2241
+ const socket = this.sockets.get(id);
2242
+ if (!socket || socket.state !== 1 || !socket.connection) throw new Error("Virtual WebSocket is not open.");
2243
+ assertFrameSize(data);
2244
+ try {
2245
+ socket.connection.send(data);
2246
+ } catch (cause) {
2247
+ this.terminate(id, socket, 1006, "", cause instanceof Error ? cause.message : String(cause));
2248
+ throw cause;
2249
+ }
2250
+ }
2251
+ close(id, code = 1e3, reason = "") {
2252
+ const socket = this.sockets.get(id);
2253
+ if (!socket) return;
2254
+ assertClose(code, reason);
2255
+ if (socket.state >= 2) return;
2256
+ socket.state = 2;
2257
+ socket.closing = { code, reason };
2258
+ clearTimeout(socket.timeout);
2259
+ socket.timeout = void 0;
2260
+ socket.closeTimer = setTimeout(() => {
2261
+ if (this.sockets.get(id) !== socket) return;
2262
+ this.terminate(id, socket, 1006, "");
2263
+ }, 100);
2264
+ this.requestClose(id, socket);
2265
+ }
2266
+ /** Host-side cancellation; it does not claim a completed close handshake. */
2267
+ abort(id, code = 1006, reason = "") {
2268
+ const socket = this.sockets.get(id);
2269
+ if (socket) this.terminate(id, socket, code, reason);
2270
+ }
2271
+ closeAll() {
2272
+ if (this.closingAll) return;
2273
+ this.closingAll = true;
2274
+ try {
2275
+ for (const [id, socket] of [...this.sockets]) this.terminate(id, socket, 1001, "Connection owner closed");
2276
+ } finally {
2277
+ this.closingAll = false;
2278
+ }
2279
+ }
2280
+ closePort(port) {
2281
+ for (const [id, socket] of [...this.sockets]) {
2282
+ if (socket.port === port) this.terminate(id, socket, 1001, "Virtual server stopped");
2283
+ }
2284
+ }
2285
+ requestClose(id, socket) {
2286
+ if (!socket.connection || !socket.closing) return;
2287
+ try {
2288
+ socket.connection.close(socket.closing.code, socket.closing.reason);
2289
+ } catch (cause) {
2290
+ this.terminate(id, socket, 1006, "", cause instanceof Error ? cause.message : String(cause));
2291
+ }
2292
+ }
2293
+ retire(id, socket) {
2294
+ if (this.sockets.get(id) !== socket) return false;
2295
+ this.sockets.delete(id);
2296
+ socket.state = 3;
2297
+ clearTimeout(socket.timeout);
2298
+ clearTimeout(socket.closeTimer);
2299
+ socket.timeout = void 0;
2300
+ socket.closeTimer = void 0;
2301
+ return true;
2302
+ }
2303
+ terminate(id, socket, code, reason, error) {
2304
+ if (!this.retire(id, socket)) return;
2305
+ this.abortConnection(socket);
2306
+ if (error !== void 0) this.notify(() => socket.handlers.error(error));
2307
+ this.notify(() => socket.handlers.close(code, reason, false));
2308
+ }
2309
+ abortConnection(socket) {
2310
+ const connection = socket.connection;
2311
+ socket.connection = void 0;
2312
+ if (!connection) return;
2313
+ try {
2314
+ if (connection.abort) connection.abort();
2315
+ else connection.close(1e3, "Connection cancelled");
2316
+ } catch (cause) {
2317
+ console.error("Virtual WebSocket transport cleanup failed.", cause);
2318
+ }
2319
+ }
2320
+ notify(callback) {
2321
+ try {
2322
+ callback();
2323
+ } catch (cause) {
2324
+ console.error("Virtual WebSocket observer failed.", cause);
2325
+ }
2326
+ }
2327
+ };
2328
+ function assertPort(port) {
2329
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65535) throw new Error("Invalid virtual WebSocket port.");
2330
+ }
2331
+ function validPath(path) {
2332
+ if (typeof path !== "string") return false;
2333
+ if (!path.startsWith("/") || path.startsWith("//") || path.length > 16384 || /[\\\u0000-\u001f\u007f]/u.test(path)) return false;
2334
+ try {
2335
+ return new URL(path, "https://virtual.invalid").origin === "https://virtual.invalid";
2336
+ } catch {
2337
+ return false;
2338
+ }
2339
+ }
2340
+ function validProtocol(protocol) {
2341
+ return protocol.length > 0 && protocol.length <= 128 && /^[!#$%&'*+\-.0-9A-Z^_`a-z|~]+$/u.test(protocol);
2342
+ }
2343
+ function assertFrameSize(data) {
2344
+ if (!data || typeof data !== "object" || data.kind !== "text" && data.kind !== "binary" || (data.kind === "text" ? typeof data.text !== "string" : !("bytes" in data ? data.bytes instanceof Uint8Array : typeof data.base64 === "string"))) {
2345
+ throw new Error("Invalid virtual WebSocket frame.");
2346
+ }
2347
+ if (data.kind === "binary" && !("bytes" in data) && data.base64.length > Math.ceil(MAX_VIRTUAL_WEBSOCKET_FRAME_BYTES / 3) * 4) {
2348
+ throw new Error("Virtual WebSocket frame exceeds 32 MiB.");
2349
+ }
2350
+ const size = data.kind === "text" ? new TextEncoder().encode(data.text).byteLength : virtualFrameByteLength(data);
2351
+ if (size === void 0) throw new Error("Invalid virtual WebSocket binary frame.");
2352
+ if (size > MAX_VIRTUAL_WEBSOCKET_FRAME_BYTES) throw new Error("Virtual WebSocket frame exceeds 32 MiB.");
2353
+ }
2354
+ function assertClose(code, reason) {
2355
+ if (!Number.isSafeInteger(code) || code !== 1e3 && (code < 3e3 || code > 4999)) {
2356
+ throw new Error("Invalid virtual WebSocket close code.");
2357
+ }
2358
+ if (typeof reason !== "string" || new TextEncoder().encode(reason).byteLength > 123) throw new Error("Virtual WebSocket close reason is invalid or too long.");
2359
+ }
2360
+ function validAuthority(value) {
2361
+ return typeof value === "string" && value.length <= 255 && /^(?:\[[0-9a-f:.]+\]|[a-z0-9.-]+)(?::[0-9]{1,5})?$/iu.test(value);
2362
+ }
2363
+
2364
+ // packages/runtime/src/browser-http.ts
2365
+ var observedBridges = /* @__PURE__ */ new WeakMap();
2366
+ function observeBrowserServerPorts(bridge, listener) {
2367
+ let observed = observedBridges.get(bridge);
2368
+ if (!observed) {
2369
+ observed = { listeners: /* @__PURE__ */ new Set(), registrations: /* @__PURE__ */ new Map() };
2370
+ observedBridges.set(bridge, observed);
2371
+ const register = bridge.registerServer.bind(bridge);
2372
+ const unregister = bridge.unregisterServer.bind(bridge);
2373
+ bridge.registerServer = (server, port, hostname) => {
2374
+ const existed = bridge.getServerPorts().includes(port);
2375
+ const handle = server.handleRequest.bind(server);
2376
+ server.handleRequest = (method, url, headers, body) => {
2377
+ const named = Object.keys(headers).some((name) => name.toLowerCase() === "host");
2378
+ return handle(method, url, named ? headers : { ...headers, host: `${hostname && hostname !== "0.0.0.0" && hostname !== "::" ? hostname : "127.0.0.1"}:${port}` }, body);
2379
+ };
2380
+ register(server, port, hostname);
2381
+ observed.registrations.set(port, { server });
2382
+ if (!existed) {
2383
+ const event = {
2384
+ port,
2385
+ type: "open",
2386
+ ...bridge.getServerUrl ? { url: bridge.getServerUrl(port) } : {}
2387
+ };
2388
+ for (const accept of observed.listeners) {
2389
+ try {
2390
+ accept(event);
2391
+ } catch (cause) {
2392
+ console.error("Port registration observer failed.", cause);
2393
+ }
2394
+ }
2395
+ }
2396
+ };
2397
+ bridge.unregisterServer = (port) => {
2398
+ const existed = bridge.getServerPorts().includes(port);
2399
+ unregister(port);
2400
+ observed.registrations.delete(port);
2401
+ if (existed) {
2402
+ const event = { port, type: "close" };
2403
+ for (const accept of observed.listeners) {
2404
+ try {
2405
+ accept(event);
2406
+ } catch (cause) {
2407
+ console.error("Port release observer failed.", cause);
2408
+ }
2409
+ }
2410
+ }
2411
+ };
2412
+ }
2413
+ observed.listeners.add(listener);
2414
+ return () => observed.listeners.delete(listener);
2415
+ }
2416
+
2417
+ // packages/runtime/src/browser-pack-index.ts
2418
+ var encoder3 = new TextEncoder();
2419
+ var decoder = new TextDecoder();
2420
+
2421
+ // packages/runtime/src/browser-pack-store.ts
2422
+ var encoder4 = new TextEncoder();
2423
+ var decoder2 = new TextDecoder("utf-8", { fatal: true });
2424
+ var MAX_RECORD_BYTES = 64 * 1024 * 1024;
2425
+ var MAX_FILE_BYTES = 1024 * 1024 * 1024;
2426
+ var MAX_LOG_BYTES = 8 * 1024 * 1024;
2427
+ var COMPACTION_FLOOR_BYTES = 64 * 1024 * 1024;
2428
+ var SESSION_COMPACTION_BYTES = 256 * 1024 * 1024;
2429
+ var COPY_BUFFER_BYTES = 4 * 1024 * 1024;
2430
+ var SWEEP_COMPACTION_BYTES = 64 * 1024 * 1024;
2431
+ var SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
2432
+ var SWEEP_GRACE_MS = 14 * 24 * 60 * 60 * 1e3;
2433
+
2434
+ // packages/runtime/src/browser-project-store.ts
2435
+ var MAX_PROJECT_BYTES = 1024 * 1024 * 1024;
2436
+ var writeTail = Promise.resolve();
2437
+
2438
+ // packages/runtime/src/http-transport-policy.ts
2439
+ var MAX_HTTP_TRANSPORT_POLICY_BYTES = 1024 * 1024;
2440
+
2441
+ // packages/runtime/src/browser-runtime-policy.ts
2442
+ var DEFAULT_BROWSER_RUNTIME_LIMITS = Object.freeze({
2443
+ maxConcurrentProcesses: 64,
2444
+ maxProcessOutputBytes: 16 * 1024 * 1024,
2445
+ maxProcessRuntimeMs: 0,
2446
+ maxVirtualPorts: 64,
2447
+ maxFilesystemEntries: 1e6,
2448
+ maxFilesystemBytes: 1024 * 1024 * 1024,
2449
+ maxVirtualRequests: 32,
2450
+ maxVirtualWebSockets: 32,
2451
+ maxVirtualRequestBytes: 16 * 1024 * 1024,
2452
+ maxVirtualResponseBytes: 32 * 1024 * 1024,
2453
+ maxPreviewCommands: 16,
2454
+ maxPreviewObservations: 1e3,
2455
+ maxEgressConnections: 16,
2456
+ maxEgressBytes: 64 * 1024 * 1024
2457
+ });
2458
+
2459
+ // packages/runtime/src/browser-preview-sockets.ts
2460
+ var BrowserPreviewSocketRelay = class {
2461
+ constructor(hub, send, limits = DEFAULT_BROWSER_RUNTIME_LIMITS) {
2462
+ this.hub = hub;
2463
+ this.send = send;
2464
+ this.limits = limits;
2465
+ }
2466
+ hub;
2467
+ send;
2468
+ limits;
2469
+ active = /* @__PURE__ */ new Set();
2470
+ get openSockets() {
2471
+ return this.active.size;
2472
+ }
2473
+ async handle(message) {
2474
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
2475
+ throw new Error("Malformed virtual WebSocket message.");
2476
+ }
2477
+ const request = message;
2478
+ if (!Number.isSafeInteger(request.id) || request.id < 1 || !request.data) {
2479
+ throw new Error("Invalid virtual WebSocket message.");
2480
+ }
2481
+ const id = request.id;
2482
+ if (request.type === "websocket-open") {
2483
+ if (this.active.size >= this.limits.maxVirtualWebSockets) {
2484
+ throw new Error("Virtual WebSocket concurrency exceeded.");
2485
+ }
2486
+ if (this.active.has(id)) throw new Error("Duplicate virtual WebSocket identifier.");
2487
+ this.active.add(id);
2488
+ try {
2489
+ this.hub.open(
2490
+ id,
2491
+ request.data.port,
2492
+ request.data.path,
2493
+ request.data.protocols ?? [],
2494
+ {
2495
+ open: (protocol) => this.send({ type: "websocket-open", id, data: { protocol } }),
2496
+ message: (frame2) => this.send({ type: "websocket-message", id, data: { frame: frame2 } }),
2497
+ close: (code, reason, clean) => {
2498
+ this.active.delete(id);
2499
+ this.send({ type: "websocket-close", id, data: { code, reason, clean } });
2500
+ },
2501
+ error: (error) => this.send({ type: "websocket-error", id, error })
2502
+ },
2503
+ typeof request.data.host === "string" ? { host: request.data.host } : void 0
2504
+ );
2505
+ } catch (cause) {
2506
+ this.active.delete(id);
2507
+ throw cause;
2508
+ }
2509
+ return;
2510
+ }
2511
+ if (!this.active.has(id)) throw new Error("Unknown virtual WebSocket identifier.");
2512
+ if (request.type === "websocket-message") {
2513
+ if (!request.data.frame) throw new Error("Virtual WebSocket message has no frame.");
2514
+ this.hub.send(id, request.data.frame);
2515
+ return;
2516
+ }
2517
+ if (request.type === "websocket-close") {
2518
+ this.hub.close(id, request.data.code, request.data.reason);
2519
+ this.active.delete(id);
2520
+ return;
2521
+ }
2522
+ throw new Error("Unknown virtual WebSocket operation.");
2523
+ }
2524
+ closeAll() {
2525
+ for (const id of [...this.active]) {
2526
+ try {
2527
+ this.hub.close(id, 1001, "The preview went away.");
2528
+ } catch {
2529
+ }
2530
+ this.active.delete(id);
2531
+ }
2532
+ }
2533
+ };
2534
+ function websocketRequestType(message) {
2535
+ const type = message?.type;
2536
+ return typeof type === "string" && type.startsWith("websocket-");
2537
+ }
2538
+
2539
+ // packages/runtime/src/browser-operation.ts
2540
+ async function waitForBrowserOperation(operation, signal) {
2541
+ const running = typeof operation === "function" ? Promise.resolve().then(() => {
2542
+ signal.throwIfAborted();
2543
+ return operation();
2544
+ }) : Promise.resolve(operation);
2545
+ let abort;
2546
+ const cancelled = new Promise((_resolve, reject) => {
2547
+ abort = () => reject(signal.reason ?? new DOMException("Browser operation was cancelled.", "AbortError"));
2548
+ signal.addEventListener("abort", abort, { once: true });
2549
+ if (signal.aborted) abort();
2550
+ });
2551
+ try {
2552
+ return await Promise.race([running, cancelled]);
2553
+ } finally {
2554
+ signal.removeEventListener("abort", abort);
2555
+ }
2556
+ }
2557
+
2558
+ // packages/runtime/src/browser-cookie-jar.ts
2559
+ var MAX_LIFETIME_MS = 400 * 24 * 60 * 60 * 1e3;
2560
+ var BROWSER_COOKIE_JAR_GLOBAL = "__browserRuntimeCookies";
2561
+
2562
+ // packages/runtime/src/browser-host-fetch.ts
2563
+ var HOST_FETCH_MAX_CHUNK_BYTES = 65536;
2564
+ var HOST_FETCH_DEFAULT_MAX_REQUEST_BYTES = 16 * 1024 * 1024;
2565
+
2566
+ // packages/runtime/src/browser-sandbox-bridge.ts
2567
+ var MAX_REQUEST_URL_CHARS = 16384;
2568
+ var MAX_REQUEST_HEADERS = 128;
2569
+ var MAX_REQUEST_HEADER_CHARS = 65536;
2570
+ var SANDBOX_HANDSHAKE_TIMEOUT_MS = 15e3;
2571
+ var SANDBOX_COMMAND_TIMEOUT_MS = 15e3;
2572
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]);
2573
+ var HTTP_HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u;
2574
+ function validateSandboxRequest(message, isRegisteredPort, limits = DEFAULT_BROWSER_RUNTIME_LIMITS) {
2575
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
2576
+ throw new Error("Malformed sandbox service-worker message.");
2577
+ }
2578
+ const request = message;
2579
+ const data = request.data;
2580
+ if (request.type !== "request" || !Number.isSafeInteger(request.id) || !data || !Number.isSafeInteger(data.port)) {
2581
+ throw new Error("Rejected an invalid virtual-server request.");
2582
+ }
2583
+ if (!isRegisteredPort(data.port)) throw new Error(`Virtual port ${data.port} is not listening.`);
2584
+ const registeredPort = data.port;
2585
+ if (!data.method || !HTTP_METHODS.has(data.method) || !data.url || data.url.length > MAX_REQUEST_URL_CHARS || !data.url.startsWith("/") || data.url.startsWith("//") || /[\\\u0000-\u001f\u007f]/u.test(data.url)) {
2586
+ throw new Error("Rejected an invalid virtual-server request target.");
2587
+ }
2588
+ const parsed = new URL(data.url, "https://virtual.invalid");
2589
+ if (parsed.origin !== "https://virtual.invalid") throw new Error("Rejected a cross-origin virtual-server request.");
2590
+ if (!data.headers || typeof data.headers !== "object" || Array.isArray(data.headers)) {
2591
+ throw new Error("Rejected invalid virtual-server request headers.");
2592
+ }
2593
+ const headerEntries = Object.entries(data.headers);
2594
+ if (headerEntries.length > MAX_REQUEST_HEADERS) throw new Error("Virtual-server request has too many headers.");
2595
+ let headerChars = 0;
2596
+ for (const [name, value] of headerEntries) {
2597
+ if (!HTTP_HEADER_NAME.test(name) || typeof value !== "string" || /[\u0000\r\n]/u.test(value)) {
2598
+ throw new Error("Rejected malformed virtual-server request headers.");
2599
+ }
2600
+ headerChars += name.length + value.length;
2601
+ }
2602
+ if (headerChars > MAX_REQUEST_HEADER_CHARS) throw new Error("Virtual-server request headers are too large.");
2603
+ if (data.body !== void 0 && data.body !== null && !(data.body instanceof ArrayBuffer)) {
2604
+ throw new Error("Rejected an invalid virtual-server request body.");
2605
+ }
2606
+ if (data.body && data.body.byteLength > limits.maxVirtualRequestBytes) {
2607
+ throw new Error(`Virtual-server request body exceeds ${limits.maxVirtualRequestBytes} bytes.`);
2608
+ }
2609
+ if (data.streaming !== void 0 && typeof data.streaming !== "boolean") throw new Error("Rejected an invalid virtual-server streaming flag.");
2610
+ if (data.flowControl !== void 0 && data.flowControl !== 1) throw new Error("Rejected an unsupported virtual-server flow-control protocol.");
2611
+ return {
2612
+ id: request.id,
2613
+ data: {
2614
+ port: registeredPort,
2615
+ method: data.method,
2616
+ url: data.url,
2617
+ headers: data.headers,
2618
+ ...data.body ? { body: data.body } : {},
2619
+ streaming: data.streaming === true,
2620
+ flowControl: data.flowControl === 1
2621
+ }
2622
+ };
2623
+ }
2624
+ function responseHeader(headers, name) {
2625
+ const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase());
2626
+ const value = entry?.[1];
2627
+ return Array.isArray(value) ? value[0] : value;
2628
+ }
2629
+ function rewriteHtmlModules(body, headers, rewrites) {
2630
+ if (!responseHeader(headers, "content-type")?.toLowerCase().includes("text/html")) return body;
2631
+ let html = new TextDecoder().decode(body);
2632
+ for (const [remote, local] of Object.entries(rewrites ?? {})) html = html.replaceAll(remote, local);
2633
+ html = injectBrowserLoopback(html);
2634
+ return new TextEncoder().encode(html);
2635
+ }
2636
+ var LOOPBACK_BOOTSTRAP_MARKER = "data-browser-runtime-loopback";
2637
+ var VIRTUAL_HOST_PARAMETER = "__browser_runtime_virtual_host";
2638
+ function injectBrowserLoopback(html) {
2639
+ if (html.includes(LOOPBACK_BOOTSTRAP_MARKER)) return html;
2640
+ const script = `<script ${LOOPBACK_BOOTSTRAP_MARKER}>(function(){
2641
+ const clip=function(value,limit){return String(value===undefined?'undefined':value===null?'null':value instanceof Error?(value.stack||value.message):typeof value==='object'?function(){try{return JSON.stringify(value);}catch{return Object.prototype.toString.call(value);}}():value).slice(0,limit);};
2642
+ const observations=[];
2643
+ Object.defineProperty(window,'__browserRuntimePreviewObservations',{value:function(){return observations.slice();}});
2644
+ const report=function(observation){try{observations.push(observation);if(observations.length>200)observations.shift();window.dispatchEvent(new CustomEvent('browser-runtime-preview-observation',{detail:observation}));window.parent.postMessage({type:'browser-agent-observation',observation:observation},location.origin);}catch{}};
2645
+ const notifyPreviewLoad=function(){
2646
+ window.parent.postMessage({type:'browser-agent-preview-load'},location.origin);
2647
+ report({kind:'navigation',url:location.href,title:document.title.slice(0,4096),timestamp:Date.now()});
2648
+ };
2649
+ if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',notifyPreviewLoad,{once:true});else queueMicrotask(notifyPreviewLoad);
2650
+ ['debug','info','log','warn','error'].forEach(function(level){
2651
+ const original=console[level].bind(console);
2652
+ console[level]=function(){const args=Array.prototype.slice.call(arguments);original.apply(console,args);report({kind:'console',level:level,message:args.map(function(value){return clip(value,8192);}).join(' ').slice(0,32768),timestamp:Date.now()});};
2653
+ });
2654
+ window.addEventListener('error',function(event){report({kind:'exception',message:clip(event.message||event.error,32768),stack:event.error?.stack?clip(event.error.stack,65536):undefined,source:event.filename?clip(event.filename,16384):undefined,line:Number.isSafeInteger(event.lineno)?event.lineno:undefined,column:Number.isSafeInteger(event.colno)?event.colno:undefined,timestamp:Date.now()});});
2655
+ window.addEventListener('unhandledrejection',function(event){const reason=event.reason;report({kind:'unhandledrejection',message:clip(reason,32768),stack:reason?.stack?clip(reason.stack,65536):undefined,timestamp:Date.now()});});
2656
+ const networkState=window.__browserRuntimeNetworkState={pending:0,lastChanged:Date.now()};
2657
+ const networkBegin=function(){networkState.pending++;networkState.lastChanged=Date.now();};
2658
+ const networkEnd=function(){networkState.pending=Math.max(0,networkState.pending-1);networkState.lastChanged=Date.now();};
2659
+ const nativeFetch=window.fetch.bind(window);
2660
+ const virtualHostParameter=${JSON.stringify(VIRTUAL_HOST_PARAMETER)};
2661
+ const route=function(value){
2662
+ try {
2663
+ const url=new URL(String(value),location.href);
2664
+ if(url.protocol!=='http:'&&url.protocol!=='https:')return value;
2665
+ // a root-relative URL from inside a virtual document is the document's
2666
+ // own server's: the page under /__virtual__/<port>/ is that server's
2667
+ // root, so /api/x is its /api/x. The service worker routed these by the
2668
+ // request's referrer, which a document sent with no referrer never has.
2669
+ if(url.origin===location.origin){
2670
+ const own=/^[/]__virtual__[/]([0-9]+)(?=[/]|$)/.exec(location.pathname)||(function(){try{return /^[/]__virtual__[/]([0-9]+)(?=[/]|$)/.exec(window.parent.location.pathname);}catch(e){return null;}})();
2671
+ if(!own||url.pathname.startsWith('/__virtual__/')) return value;
2672
+ const routed=new URL(location.origin+'/__virtual__/'+own[1]+url.pathname+url.search);
2673
+ // The host this document is answered as is the session's, not this one
2674
+ // URL's: a page that answers as one host asked for its own assets as
2675
+ // another, and the server sent them down a different route entirely.
2676
+ const carried=new URLSearchParams(location.search).get(virtualHostParameter);
2677
+ if(carried&&!routed.searchParams.has(virtualHostParameter))routed.searchParams.set(virtualHostParameter,carried);
2678
+ routed.hash=url.hash;
2679
+ return routed.href;
2680
+ }
2681
+ const loopback=url.hostname==='localhost'||url.hostname.endsWith('.localhost')||url.hostname==='127.0.0.1'||url.hostname==='[::1]';
2682
+ if((url.protocol==='http:'||url.protocol==='https:')&&loopback){
2683
+ const port=url.port||String(url.protocol==='https:'?443:80);
2684
+ const routed=new URL(location.origin+'/__virtual__/'+port+url.pathname+url.search);
2685
+ routed.searchParams.set(virtualHostParameter,url.host);
2686
+ routed.hash=url.hash;
2687
+ return routed.href;
2688
+ }
2689
+ } catch {}
2690
+ return value;
2691
+ };
2692
+ // document.cookie is the tab's jar (ADR-0050): the host's cookies live there,
2693
+ // not in this origin's, and a server here set them through its answers. The
2694
+ // jar is offered by a same-origin ancestor, the page that holds the tab; a
2695
+ // frame of another origin cannot read it, and keeps the browser's own.
2696
+ (function(){
2697
+ let cookies=null;
2698
+ try{for(let owner=window.parent;;owner=owner.parent){const offered=owner[${JSON.stringify(BROWSER_COOKIE_JAR_GLOBAL)}];if(offered&&typeof offered.read==='function'){cookies=offered;break;}if(owner===owner.parent)break;}}catch{}
2699
+ if(!cookies)return;
2700
+ const where=function(){
2701
+ const host=new URLSearchParams(location.search).get(virtualHostParameter)||location.host;
2702
+ const path=location.pathname.replace(/^[/]__virtual__[/][0-9]+(?=[/]|$)/,'')||'/';
2703
+ return {host:host,path:path};
2704
+ };
2705
+ Object.defineProperty(document,'cookie',{configurable:true,enumerable:true,get:function(){return cookies.read(where());},set:function(value){cookies.write(where(),String(value));}});
2706
+ })();
2707
+ window.fetch=function(input,init){
2708
+ if(input instanceof Request){
2709
+ const target=route(input.url);
2710
+ networkBegin();
2711
+ try{return nativeFetch(target===input.url?input:new Request(target,input),init).finally(networkEnd);}catch(error){networkEnd();throw error;}
2712
+ }
2713
+ networkBegin();
2714
+ try{return nativeFetch(route(input),init).finally(networkEnd);}catch(error){networkEnd();throw error;}
2715
+ };
2716
+ const nativeOpen=XMLHttpRequest.prototype.open;
2717
+ XMLHttpRequest.prototype.open=function(method,url){
2718
+ const args=Array.prototype.slice.call(arguments);
2719
+ args[1]=route(url);
2720
+ return nativeOpen.apply(this,args);
2721
+ };
2722
+ const nativeSend=XMLHttpRequest.prototype.send;
2723
+ XMLHttpRequest.prototype.send=function(){networkBegin();this.addEventListener('loadend',networkEnd,{once:true});try{return nativeSend.apply(this,arguments);}catch(error){networkEnd();throw error;}};
2724
+ // A beacon is a request too: vgai's editor sends its heartbeat with one, and
2725
+ // a root-relative beacon that is not routed reaches the page's origin, which
2726
+ // answers 405 once a second.
2727
+ if(typeof navigator.sendBeacon==='function'){
2728
+ const nativeSendBeacon=navigator.sendBeacon.bind(navigator);
2729
+ navigator.sendBeacon=function(url,data){return data===undefined?nativeSendBeacon(route(url)):nativeSendBeacon(route(url),data);};
2730
+ }
2731
+ // A worker a virtual document makes is that document's server's: its script
2732
+ // URL routed like any request, so the worker's own requests carry a referrer
2733
+ // that names the port and the service worker attributes them. Made from the
2734
+ // origin root, a worker's root-relative fetch reached the page's origin.
2735
+ // A blob has no script fetch carrying the virtual path. Keep its creator
2736
+ // identity for the service worker to query, including after SW restarts.
2737
+ // Keep URL names after revoke: an already-running worker still uses that URL.
2738
+ const createdObjectUrls=new Set();
2739
+ const nativeCreateObjectURL=URL.createObjectURL.bind(URL);
2740
+ URL.createObjectURL=function(value){const url=nativeCreateObjectURL(value);createdObjectUrls.add(url);return url;};
2741
+ if(navigator.serviceWorker)navigator.serviceWorker.addEventListener('message',function(event){
2742
+ if(event.data?.type!=='virtual-blob-owner'||!event.ports[0])return;
2743
+ const url=String(event.data.url||'').split('#')[0];
2744
+ event.ports[0].postMessage(createdObjectUrls.has(url));
2745
+ event.ports[0].close();
2746
+ });
2747
+ for(const name of ['Worker','SharedWorker']){
2748
+ const Native=window[name];
2749
+ if(typeof Native!=='function')continue;
2750
+ const Routed=function(url,options){return options===undefined?new Native(route(url)):new Native(route(url),options);};
2751
+ Routed.prototype=Native.prototype;
2752
+ window[name]=Routed;
2753
+ }
2754
+ if(typeof EventSource==='function'){
2755
+ const NativeEventSource=EventSource;
2756
+ window.EventSource=function(url,options){return new NativeEventSource(route(url),options);};
2757
+ window.EventSource.prototype=NativeEventSource.prototype;
2758
+ Object.defineProperties(window.EventSource,{CONNECTING:{value:0},OPEN:{value:1},CLOSED:{value:2}});
2759
+ }
2760
+ const NativeWebSocket=window.WebSocket;
2761
+ let virtualWebSocketSequence=0;
2762
+ // A document numbers its sockets from a base of its own: the document before
2763
+ // it in the frame may still hold sockets the host has not finished, and a
2764
+ // number shared with one of those is refused as a duplicate.
2765
+ const virtualWebSocketBase=Math.floor(Math.random()*2147483648)*1048576;
2766
+ const virtualWebSockets=new Map();
2767
+ const ownBytes=function(value){
2768
+ const view=value instanceof ArrayBuffer?new Uint8Array(value):new Uint8Array(value.buffer,value.byteOffset,value.byteLength);
2769
+ return view.slice();
2770
+ };
2771
+ const decodeBinary=function(value){
2772
+ if(typeof Uint8Array.fromBase64==='function')return Uint8Array.fromBase64(value);
2773
+ const binary=atob(value);
2774
+ const out=new Uint8Array(binary.length);
2775
+ for(let index=0;index<binary.length;index+=1)out[index]=binary.charCodeAt(index);
2776
+ return out;
2777
+ };
2778
+ const emitSocket=function(socket,type,event){
2779
+ socket.dispatchEvent(event);
2780
+ const handler=socket['on'+type];
2781
+ if(typeof handler==='function')handler.call(socket,event);
2782
+ };
2783
+ class VirtualWebSocket extends EventTarget{
2784
+ constructor(url,protocols){
2785
+ super();
2786
+ const parsed=new URL(String(url),location.href);
2787
+ this.url=parsed.href;
2788
+ this.readyState=0;
2789
+ this.bufferedAmount=0;
2790
+ this.extensions='';
2791
+ this.protocol='';
2792
+ this.binaryType='blob';
2793
+ this.onopen=this.onmessage=this.onerror=this.onclose=null;
2794
+ this.__id=virtualWebSocketBase+(++virtualWebSocketSequence);
2795
+ virtualWebSockets.set(this.__id,this);
2796
+ // A socket to the page's own host from a frame served under a virtual
2797
+ // port is a socket to that port: a dev server's HMR client computes its
2798
+ // URL from location, which names the page's origin, not the server's.
2799
+ // A document at the origin's root has no prefix to name its server: 0
2800
+ // says its own, and the sandbox that opened it fills the port in.
2801
+ const virtualMatch=/^[/]__virtual__[/]([0-9]+)(?=[/]|$)/.exec(location.pathname);
2802
+ const ownHost=parsed.host===location.host||(parsed.hostname===location.hostname&&parsed.port==='');
2803
+ const port=ownHost?(virtualMatch?Number(virtualMatch[1]):0):Number(parsed.port||((parsed.protocol==='wss:')?443:80));
2804
+ const selected=protocols===undefined?[]:(Array.isArray(protocols)?protocols:[protocols]);
2805
+ window.parent.postMessage({type:'websocket-open',id:this.__id,data:{port:port,host:parsed.host,path:parsed.pathname+parsed.search,protocols:selected.map(String)}},location.origin);
2806
+ }
2807
+ send(value){
2808
+ if(this.readyState!==1)throw new DOMException('WebSocket is not open.','InvalidStateError');
2809
+ if(value instanceof Blob){
2810
+ this.bufferedAmount+=value.size;
2811
+ void value.arrayBuffer().then((buffer)=>{this.bufferedAmount=Math.max(0,this.bufferedAmount-value.size);this.__send({kind:'binary',bytes:new Uint8Array(buffer)});});
2812
+ return;
2813
+ }
2814
+ // Bytes cross to the page as they are (a copy the socket owns), not as base64.
2815
+ const frame=typeof value==='string'?{kind:'text',text:value}:{kind:'binary',bytes:ownBytes(value)};
2816
+ this.__send(frame);
2817
+ }
2818
+ __send(frame){window.parent.postMessage({type:'websocket-message',id:this.__id,data:{frame:frame}},location.origin);}
2819
+ close(code,reason){
2820
+ if(this.readyState===2||this.readyState===3)return;
2821
+ this.readyState=2;
2822
+ window.parent.postMessage({type:'websocket-close',id:this.__id,data:{code:code===undefined?1000:code,reason:reason===undefined?'':String(reason)}},location.origin);
2823
+ }
2824
+ }
2825
+ Object.defineProperties(VirtualWebSocket,{CONNECTING:{value:0},OPEN:{value:1},CLOSING:{value:2},CLOSED:{value:3}});
2826
+ Object.defineProperties(VirtualWebSocket.prototype,{CONNECTING:{value:0},OPEN:{value:1},CLOSING:{value:2},CLOSED:{value:3}});
2827
+ window.addEventListener('message',function(event){
2828
+ if(event.source!==window.parent||event.origin!==location.origin||typeof event.data?.type!=='string'||!event.data.type.startsWith('websocket-'))return;
2829
+ const message=event.data;
2830
+ const socket=virtualWebSockets.get(message.id);
2831
+ if(!socket)return;
2832
+ if(message.type==='websocket-open'){
2833
+ socket.readyState=1;
2834
+ socket.protocol=String(message.data?.protocol||'');
2835
+ emitSocket(socket,'open',new Event('open'));
2836
+ }else if(message.type==='websocket-message'){
2837
+ const frame=message.data?.frame;
2838
+ let data=frame?.kind==='text'?String(frame.text):(frame?.bytes instanceof Uint8Array?frame.bytes:decodeBinary(String(frame?.base64||'')));
2839
+ if(data instanceof Uint8Array&&(data.byteOffset!==0||data.byteLength!==data.buffer.byteLength))data=data.slice();
2840
+ if(frame?.kind==='binary')data=socket.binaryType==='arraybuffer'?data.buffer:new Blob([data]);
2841
+ emitSocket(socket,'message',new MessageEvent('message',{data:data}));
2842
+ }else if(message.type==='websocket-close'){
2843
+ socket.readyState=3;
2844
+ virtualWebSockets.delete(message.id);
2845
+ emitSocket(socket,'close',new CloseEvent('close',{code:message.data?.code||1000,reason:String(message.data?.reason||''),wasClean:message.data?.clean!==false}));
2846
+ }else if(message.type==='websocket-error'){
2847
+ emitSocket(socket,'error',new ErrorEvent('error',{message:String(message.error||'Virtual WebSocket failed.')}));
2848
+ if(socket.readyState!==3){
2849
+ socket.readyState=3;
2850
+ virtualWebSockets.delete(message.id);
2851
+ emitSocket(socket,'close',new CloseEvent('close',{code:1006,reason:'',wasClean:false}));
2852
+ }
2853
+ }
2854
+ });
2855
+ // A document leaving the frame takes its sockets with it, as a browser
2856
+ // closes a page's sockets; the next document starts its numbering over.
2857
+ window.addEventListener('pagehide',function(){
2858
+ virtualWebSockets.forEach(function(socket){
2859
+ if(socket.readyState===0||socket.readyState===1)socket.close(1001,'Page hidden');
2860
+ });
2861
+ });
2862
+ window.WebSocket=function(url,protocols){
2863
+ const parsed=new URL(String(url),location.href);
2864
+ const local=(parsed.protocol==='ws:'||parsed.protocol==='wss:')&&(parsed.host===location.host||parsed.hostname==='localhost'||parsed.hostname.endsWith('.localhost')||parsed.hostname==='127.0.0.1'||parsed.hostname==='[::1]');
2865
+ // The host answers from the frame's parent. A document with no parent (the
2866
+ // app opened by its share link, a preview in a tab of its own) has no host
2867
+ // to answer: its messages would come back to itself, every frame an echo,
2868
+ // and its sockets go out as the browser's own.
2869
+ if(local&&window.parent!==window)return new VirtualWebSocket(parsed.href,protocols);
2870
+ return protocols===undefined?new NativeWebSocket(parsed.href):new NativeWebSocket(parsed.href,protocols);
2871
+ };
2872
+ window.WebSocket.prototype=VirtualWebSocket.prototype;
2873
+ Object.defineProperties(window.WebSocket,{CONNECTING:{value:0},OPEN:{value:1},CLOSING:{value:2},CLOSED:{value:3}});
2874
+ })();<\/script>`;
2875
+ if (/<head(?:\s[^>]*)?>/iu.test(html)) return html.replace(/<head(?:\s[^>]*)?>/iu, (head) => `${head}
2876
+ ${script}`);
2877
+ return `${script}
2878
+ ${html}`;
2879
+ }
2880
+ var BrowserSandboxBridge = class {
2881
+ constructor(options) {
2882
+ this.options = options;
2883
+ this.port = options.port;
2884
+ this.ready = new Promise((resolve, reject) => {
2885
+ this.resolveReady = resolve;
2886
+ this.rejectReady = reject;
2887
+ });
2888
+ this.initialPreviewReady = new Promise((resolve, reject) => {
2889
+ this.resolveInitialPreview = resolve;
2890
+ this.rejectInitialPreview = reject;
2891
+ });
2892
+ void this.ready.catch(() => {
2893
+ });
2894
+ void this.initialPreviewReady.catch(() => {
2895
+ });
2896
+ this.webSockets = new BrowserVirtualWebSocketHub(
2897
+ (port) => this.options.bridge.getServerPorts().includes(port),
2898
+ this.options.webSocketRegistry,
2899
+ this.options.webSocketConnector
2900
+ );
2901
+ this.socketRelay = new BrowserPreviewSocketRelay(this.webSockets, (message) => this.send(message), this.limits);
2902
+ this.stopObservingPorts = observeBrowserServerPorts(this.options.bridge, (event) => {
2903
+ if (event.type === "close") {
2904
+ this.webSockets.closePort(event.port);
2905
+ for (const request of this.requestControllers.values()) {
2906
+ if (request.port === event.port) request.controller.abort(new Error("The preview server stopped."));
2907
+ }
2908
+ }
2909
+ });
2910
+ this.stopObservingHostServices = options.hostFetchServices?.observe((port, capability) => {
2911
+ if (!this.bridgeConnected) return;
2912
+ this.send(capability ? { type: "server-registered", data: { port, hostname: "127.0.0.1", ...capability } } : this.options.bridge.getServerPorts().includes(port) ? { type: "server-registered", data: { port, hostname: "127.0.0.1" } } : { type: "server-unregistered", data: { port } });
2913
+ }) ?? (() => void 0);
2914
+ window.addEventListener("message", this.onMessage);
2915
+ }
2916
+ options;
2917
+ ready;
2918
+ initialPreviewReady;
2919
+ resolveReady;
2920
+ rejectReady;
2921
+ resolveInitialPreview;
2922
+ rejectInitialPreview;
2923
+ activeRequestIds = /* @__PURE__ */ new Set();
2924
+ /** Requests past the concurrency limit, each released as a slot frees. */
2925
+ waitingRequests = [];
2926
+ activeWebSocketIds = /* @__PURE__ */ new Set();
2927
+ activeCommands = /* @__PURE__ */ new Map();
2928
+ observationBuffer = [];
2929
+ webSockets;
2930
+ socketRelay;
2931
+ stopObservingPorts;
2932
+ requestControllers = /* @__PURE__ */ new Map();
2933
+ hostFlowRequests = /* @__PURE__ */ new Map();
2934
+ stopObservingHostServices;
2935
+ readyTimer;
2936
+ initialPreviewTimer;
2937
+ readyFinished = false;
2938
+ initialPreviewFinished = false;
2939
+ bridgeConnected = false;
2940
+ previewConnected = false;
2941
+ announcedReady = false;
2942
+ readinessWaiters = /* @__PURE__ */ new Set();
2943
+ disposed = false;
2944
+ nextCommandId = 1;
2945
+ protocolChannel;
2946
+ cancelProtocolAttachment;
2947
+ protocolDocumentId;
2948
+ port;
2949
+ /** A bounded observation of current readiness; a timeout does not poison later observations. */
2950
+ waitForReady(timeoutMs = SANDBOX_HANDSHAKE_TIMEOUT_MS) {
2951
+ this.assertActive();
2952
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Invalid sandbox readiness timeout.");
2953
+ if (this.bridgeConnected && this.previewConnected) return Promise.resolve();
2954
+ return new Promise((resolve, reject) => {
2955
+ const finish = (error) => {
2956
+ globalThis.clearTimeout(timer);
2957
+ this.readinessWaiters.delete(waiter);
2958
+ if (error) reject(error);
2959
+ else resolve();
2960
+ };
2961
+ const waiter = { resolve: () => finish(), reject: (error) => finish(error) };
2962
+ const timer = globalThis.setTimeout(() => finish(new Error("Sandbox preview connection timed out.")), timeoutMs);
2963
+ this.readinessWaiters.add(waiter);
2964
+ });
2965
+ }
2966
+ setPort(port) {
2967
+ this.assertActive();
2968
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65535) throw new Error("Invalid virtual server port.");
2969
+ this.port = port;
2970
+ this.armStartupTimeouts();
2971
+ }
2972
+ snapshot() {
2973
+ return this.command({ operation: "snapshot" });
2974
+ }
2975
+ query(selector) {
2976
+ return this.command({ operation: "query", selector });
2977
+ }
2978
+ screenshot(options = {}) {
2979
+ return this.command({ operation: "screenshot", ...options });
2980
+ }
2981
+ navigate(url) {
2982
+ return this.command({ operation: "navigate", url });
2983
+ }
2984
+ reloadPreview() {
2985
+ return this.command({ operation: "reload" });
2986
+ }
2987
+ waitForNetworkIdle(options = {}) {
2988
+ return this.command({
2989
+ operation: "network-idle",
2990
+ ...options.idleMs !== void 0 ? { idleMs: options.idleMs } : {},
2991
+ ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {}
2992
+ }, (options.timeoutMs ?? 1e4) + 1e3);
2993
+ }
2994
+ setPreviewScript(source) {
2995
+ return this.command({ operation: "set-script", ...source !== void 0 ? { source } : {} });
2996
+ }
2997
+ /** Execute trusted host instrumentation in the app's realm, returning JSON. */
2998
+ evaluatePreview(source, args) {
2999
+ return this.command({ operation: "evaluate", source, ...args !== void 0 ? { args } : {} });
3000
+ }
3001
+ observations() {
3002
+ return this.observationBuffer.slice();
3003
+ }
3004
+ clearObservations() {
3005
+ this.observationBuffer.length = 0;
3006
+ }
3007
+ /** Lend the authenticated preview's protocol surface to a Host dispatcher. */
3008
+ /**
3009
+ * Lends the authenticated preview's protocol surface: an owned MessagePort
3010
+ * whose far end the sandbox gave the surface its embedding registered, and
3011
+ * `closed`, settled with a reason when the bridge takes the lending back
3012
+ * (the sandbox document unloaded or was replaced, the surface closed, a
3013
+ * newer lending replaced it, or the bridge was disposed). Messages are
3014
+ * `{type:"data", data}` and `{type:"close", code, reason}`.
3015
+ */
3016
+ async connectProtocol(id = globalThis.crypto.randomUUID(), timeoutMs = SANDBOX_HANDSHAKE_TIMEOUT_MS) {
3017
+ this.assertActive();
3018
+ if (!/^[a-zA-Z0-9_-]{1,128}$/u.test(id)) throw new Error("Invalid preview target identifier.");
3019
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Invalid protocol attachment timeout.");
3020
+ await this.waitForReady(timeoutMs);
3021
+ this.assertActive();
3022
+ if (this.cancelProtocolAttachment) throw new Error("A preview protocol attachment is already in flight.");
3023
+ this.protocolChannel?.finish("Preview protocol replaced");
3024
+ const target = this.options.preview.contentWindow;
3025
+ if (!target) throw new Error("The preview frame is no longer available.");
3026
+ const channel = new MessageChannel();
3027
+ return new Promise((resolve, reject) => {
3028
+ const cleanup = () => {
3029
+ globalThis.clearTimeout(timer);
3030
+ channel.port1.removeEventListener("message", acknowledged);
3031
+ channel.port1.removeEventListener("messageerror", failed);
3032
+ if (this.cancelProtocolAttachment === cancel) this.cancelProtocolAttachment = void 0;
3033
+ };
3034
+ const cancel = (error) => {
3035
+ cleanup();
3036
+ channel.port1.postMessage({ type: "close", code: 1001, reason: error.message });
3037
+ channel.port1.close();
3038
+ channel.port2.close();
3039
+ reject(error);
3040
+ };
3041
+ const failed = () => cancel(new Error("Preview protocol acknowledgement could not be decoded."));
3042
+ const acknowledged = (event) => {
3043
+ if (event.data?.type !== "protocol-ready" || event.data?.id !== id) {
3044
+ cancel(new Error(event.data?.type === "close" ? String(event.data.reason) : "Invalid preview protocol acknowledgement."));
3045
+ return;
3046
+ }
3047
+ cleanup();
3048
+ let settle;
3049
+ const closed = new Promise((resolveClosed) => {
3050
+ settle = resolveClosed;
3051
+ });
3052
+ let done = false;
3053
+ const closedByPreview = (message) => {
3054
+ if (message.data?.type === "close") entry.finish(String(message.data.reason ?? "Preview protocol closed"), true);
3055
+ };
3056
+ const entry = {
3057
+ finish: (reason, fromPreview = false) => {
3058
+ if (done) return;
3059
+ done = true;
3060
+ channel.port1.removeEventListener("message", closedByPreview);
3061
+ if (!fromPreview) {
3062
+ try {
3063
+ channel.port1.postMessage({ type: "close", code: 1001, reason });
3064
+ } catch {
3065
+ }
3066
+ }
3067
+ if (this.protocolChannel === entry) this.protocolChannel = void 0;
3068
+ settle(reason);
3069
+ }
3070
+ };
3071
+ channel.port1.addEventListener("message", closedByPreview);
3072
+ this.protocolChannel = entry;
3073
+ resolve({ port: channel.port1, closed });
3074
+ };
3075
+ const timer = globalThis.setTimeout(() => cancel(new Error("Preview protocol attachment timed out.")), timeoutMs);
3076
+ this.cancelProtocolAttachment = cancel;
3077
+ channel.port1.addEventListener("message", acknowledged);
3078
+ channel.port1.addEventListener("messageerror", failed);
3079
+ channel.port1.start();
3080
+ try {
3081
+ target.postMessage(
3082
+ { channel: SANDBOX_CHANNEL, nonce: this.options.nonce, type: "protocol-connect", payload: { id } },
3083
+ this.options.origin,
3084
+ [channel.port2]
3085
+ );
3086
+ } catch (cause) {
3087
+ cancel(cause instanceof Error ? cause : new Error(String(cause)));
3088
+ }
3089
+ });
3090
+ }
3091
+ resetProtocol(reason) {
3092
+ this.cancelProtocolAttachment?.(new Error(reason));
3093
+ this.protocolChannel?.finish(reason);
3094
+ }
3095
+ dispose() {
3096
+ if (this.disposed) return;
3097
+ this.disposed = true;
3098
+ this.resetProtocol("Preview bridge disposed");
3099
+ window.removeEventListener("message", this.onMessage);
3100
+ this.stopObservingPorts();
3101
+ this.stopObservingHostServices();
3102
+ this.webSockets.closeAll();
3103
+ for (const request of this.requestControllers.values()) request.controller.abort(new Error("The preview bridge was disposed."));
3104
+ for (const request of this.hostFlowRequests.values()) {
3105
+ request.closed = true;
3106
+ if (request.reader) request.track(request.reader.cancel(), true);
3107
+ request.controller.abort(new Error("The preview bridge was disposed."));
3108
+ request.wake?.();
3109
+ }
3110
+ this.requestControllers.clear();
3111
+ this.activeRequestIds.clear();
3112
+ if (this.readyTimer !== void 0) globalThis.clearTimeout(this.readyTimer);
3113
+ if (this.initialPreviewTimer !== void 0) globalThis.clearTimeout(this.initialPreviewTimer);
3114
+ const error = new Error("Browser sandbox bridge was disposed.");
3115
+ if (!this.readyFinished) {
3116
+ this.readyFinished = true;
3117
+ this.rejectReady(error);
3118
+ }
3119
+ if (!this.initialPreviewFinished) {
3120
+ this.initialPreviewFinished = true;
3121
+ this.rejectInitialPreview(error);
3122
+ }
3123
+ for (const waiter of this.readinessWaiters) waiter.reject(error);
3124
+ for (const pending of this.activeCommands.values()) {
3125
+ globalThis.clearTimeout(pending.timeout);
3126
+ pending.reject(error);
3127
+ }
3128
+ this.activeCommands.clear();
3129
+ }
3130
+ onMessage = (event) => {
3131
+ if (this.disposed || event.source !== this.options.preview.contentWindow || event.origin !== this.options.origin || !isSandboxEnvelope(event.data, this.options.nonce)) return;
3132
+ const envelope = event.data;
3133
+ if (envelope.type === "request") {
3134
+ const flowControl = envelope.payload?.type;
3135
+ if (flowControl === "stream-pull" || flowControl === "stream-cancel") {
3136
+ this.handleHostFlowControl(envelope.payload);
3137
+ return;
3138
+ }
3139
+ const websocket = websocketRequestType(envelope.payload);
3140
+ const initialNavigation = !websocket && isInitialPreviewNavigation(envelope.payload);
3141
+ const operation = websocket ? this.handleWebSocket(envelope.payload) : this.handleRequest(envelope.payload);
3142
+ void operation.then(() => {
3143
+ if (initialNavigation) this.finishInitialPreview();
3144
+ }).catch((error) => {
3145
+ const requestId = envelope.payload?.id;
3146
+ this.send({
3147
+ type: websocket ? "websocket-error" : "response",
3148
+ id: requestId,
3149
+ error: error instanceof Error ? error.message : String(error)
3150
+ });
3151
+ });
3152
+ } else if (envelope.type === "protocol-disconnect") {
3153
+ this.resetProtocol("Preview document unloaded");
3154
+ this.bridgeConnected = false;
3155
+ this.previewConnected = false;
3156
+ } else if (envelope.type === "ready") {
3157
+ const documentId = envelope.payload?.documentId;
3158
+ if (typeof documentId === "string" && documentId !== this.protocolDocumentId) {
3159
+ if (this.protocolDocumentId !== void 0) this.resetProtocol("Preview document replaced");
3160
+ this.protocolDocumentId = documentId;
3161
+ }
3162
+ this.bridgeConnected = true;
3163
+ for (const service of this.options.hostFetchServices?.snapshot() ?? []) {
3164
+ this.send({ type: "server-registered", data: { port: service.port, hostname: "127.0.0.1", ...service.capability } });
3165
+ }
3166
+ if (!this.readyFinished) {
3167
+ this.readyFinished = true;
3168
+ if (this.readyTimer !== void 0) globalThis.clearTimeout(this.readyTimer);
3169
+ this.resolveReady();
3170
+ }
3171
+ this.announceReady();
3172
+ } else if (envelope.type === "preview-load") {
3173
+ this.finishInitialPreview();
3174
+ } else if (envelope.type === "rendered") {
3175
+ const revision = envelope.payload?.revision;
3176
+ if (typeof revision === "string" && revision.length > 0 && revision.length <= 128) this.options.onRendered?.(revision);
3177
+ } else if (envelope.type === "command-result") {
3178
+ this.finishCommand(envelope.payload);
3179
+ } else if (envelope.type === "observation") {
3180
+ const observation = validatePreviewObservation(envelope.payload);
3181
+ if (!observation) return;
3182
+ this.observationBuffer.push(observation);
3183
+ if (this.observationBuffer.length > this.limits.maxPreviewObservations) this.observationBuffer.shift();
3184
+ this.options.onObservation?.(observation);
3185
+ } else if (envelope.type === "error") {
3186
+ const message = envelope.payload?.message || "Sandbox bridge failed.";
3187
+ const error = new Error(message);
3188
+ for (const waiter of this.readinessWaiters) waiter.reject(error);
3189
+ if (!this.readyFinished) {
3190
+ this.readyFinished = true;
3191
+ if (this.readyTimer !== void 0) globalThis.clearTimeout(this.readyTimer);
3192
+ this.rejectReady(error);
3193
+ }
3194
+ if (!this.initialPreviewFinished) {
3195
+ this.initialPreviewFinished = true;
3196
+ if (this.initialPreviewTimer !== void 0) globalThis.clearTimeout(this.initialPreviewTimer);
3197
+ this.rejectInitialPreview(error);
3198
+ }
3199
+ for (const pending of this.activeCommands.values()) {
3200
+ globalThis.clearTimeout(pending.timeout);
3201
+ pending.reject(error);
3202
+ }
3203
+ this.activeCommands.clear();
3204
+ this.options.onError?.(error);
3205
+ }
3206
+ };
3207
+ async command(input, timeoutMs = SANDBOX_COMMAND_TIMEOUT_MS) {
3208
+ this.assertActive();
3209
+ await this.waitForReady();
3210
+ this.assertActive();
3211
+ if (this.activeCommands.size >= this.limits.maxPreviewCommands) {
3212
+ throw new Error("Preview command concurrency exceeded.");
3213
+ }
3214
+ const id = this.nextCommandId++;
3215
+ const command = { ...input, id };
3216
+ return new Promise((resolve, reject) => {
3217
+ const timeout = globalThis.setTimeout(() => {
3218
+ this.activeCommands.delete(id);
3219
+ reject(new Error(`Preview command ${input.operation} timed out.`));
3220
+ }, timeoutMs);
3221
+ this.activeCommands.set(id, {
3222
+ resolve: (value) => resolve(value),
3223
+ reject,
3224
+ timeout
3225
+ });
3226
+ try {
3227
+ const target = this.options.preview.contentWindow;
3228
+ if (!target) throw new Error("The preview frame is no longer available.");
3229
+ target.postMessage({
3230
+ channel: SANDBOX_CHANNEL,
3231
+ nonce: this.options.nonce,
3232
+ type: "command",
3233
+ payload: command
3234
+ }, this.options.origin);
3235
+ } catch (cause) {
3236
+ globalThis.clearTimeout(timeout);
3237
+ this.activeCommands.delete(id);
3238
+ reject(cause);
3239
+ }
3240
+ });
3241
+ }
3242
+ finishCommand(payload) {
3243
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return;
3244
+ const result = payload;
3245
+ if (!Number.isSafeInteger(result.id)) return;
3246
+ const pending = this.activeCommands.get(result.id);
3247
+ if (!pending) return;
3248
+ this.activeCommands.delete(result.id);
3249
+ globalThis.clearTimeout(pending.timeout);
3250
+ if (typeof result.error === "string") pending.reject(new Error(result.error));
3251
+ else pending.resolve(result.result);
3252
+ }
3253
+ assertActive() {
3254
+ if (this.disposed) throw new Error("Browser sandbox bridge is disposed.");
3255
+ }
3256
+ get limits() {
3257
+ return this.options.policy?.limits ?? DEFAULT_BROWSER_RUNTIME_LIMITS;
3258
+ }
3259
+ armStartupTimeouts() {
3260
+ if (!this.readyFinished && this.readyTimer === void 0) {
3261
+ this.readyTimer = globalThis.setTimeout(() => {
3262
+ this.readyTimer = void 0;
3263
+ this.readyFinished = true;
3264
+ this.rejectReady(new Error("Sandbox bridge handshake timed out."));
3265
+ }, SANDBOX_HANDSHAKE_TIMEOUT_MS);
3266
+ }
3267
+ if (!this.initialPreviewFinished && this.initialPreviewTimer === void 0) {
3268
+ this.initialPreviewTimer = globalThis.setTimeout(() => {
3269
+ this.initialPreviewTimer = void 0;
3270
+ this.initialPreviewFinished = true;
3271
+ this.rejectInitialPreview(new Error("Generated preview startup timed out."));
3272
+ }, SANDBOX_HANDSHAKE_TIMEOUT_MS);
3273
+ }
3274
+ }
3275
+ finishInitialPreview() {
3276
+ if (this.disposed || this.previewConnected) return;
3277
+ this.previewConnected = true;
3278
+ if (this.initialPreviewTimer !== void 0) globalThis.clearTimeout(this.initialPreviewTimer);
3279
+ if (!this.initialPreviewFinished) {
3280
+ this.initialPreviewFinished = true;
3281
+ this.resolveInitialPreview();
3282
+ }
3283
+ this.options.onPreviewLoad?.();
3284
+ this.announceReady();
3285
+ }
3286
+ announceReady() {
3287
+ if (!this.bridgeConnected || !this.previewConnected) return;
3288
+ for (const waiter of this.readinessWaiters) waiter.resolve();
3289
+ if (!this.announcedReady) {
3290
+ this.announcedReady = true;
3291
+ this.options.onReady?.();
3292
+ }
3293
+ }
3294
+ send(message) {
3295
+ if (this.disposed) return;
3296
+ this.options.preview.contentWindow?.postMessage({
3297
+ channel: SANDBOX_CHANNEL,
3298
+ nonce: this.options.nonce,
3299
+ type: "response",
3300
+ payload: message
3301
+ }, this.options.origin);
3302
+ }
3303
+ handleHostFlowControl(message) {
3304
+ if (!message || typeof message !== "object") return;
3305
+ const control = message;
3306
+ if (!Number.isSafeInteger(control.id)) return;
3307
+ const request = this.hostFlowRequests.get(control.id);
3308
+ if (!request || request.closed) return;
3309
+ if (control.type === "stream-cancel") {
3310
+ request.closed = true;
3311
+ if (request.reader) request.track(request.reader.cancel(), true);
3312
+ request.controller.abort(new Error("The guest cancelled the Host Fetch response."));
3313
+ request.wake?.();
3314
+ } else if (control.type === "stream-pull") {
3315
+ if (request.credit) return;
3316
+ request.credit = true;
3317
+ request.wake?.();
3318
+ }
3319
+ }
3320
+ async handleHostFlowRequest(request, controller) {
3321
+ const service = this.options.hostFetchServices?.get(request.data.port);
3322
+ if (!service || !request.data.flowControl || !request.data.streaming) {
3323
+ throw new Error("Host Fetch service requires flow-control protocol 1.");
3324
+ }
3325
+ if ((request.data.body?.byteLength ?? 0) > service.maxRequestBytes) {
3326
+ throw new Error(`Host Fetch-service upload exceeds ${service.maxRequestBytes} bytes.`);
3327
+ }
3328
+ const state = {
3329
+ port: service.port,
3330
+ controller,
3331
+ credit: false,
3332
+ closed: false,
3333
+ track: (operation, critical) => service.track(operation, critical)
3334
+ };
3335
+ this.hostFlowRequests.set(request.id, state);
3336
+ let cleanupFailure;
3337
+ const abortOwner = () => controller.abort(service.signal.reason);
3338
+ service.signal.addEventListener("abort", abortOwner, { once: true });
3339
+ try {
3340
+ if (service.signal.aborted) abortOwner();
3341
+ if (service.ready) await waitForBrowserOperation(service.ready, controller.signal);
3342
+ const method = request.data.method;
3343
+ const url = `http://127.0.0.1:${service.port}${request.data.url}`;
3344
+ const running = Promise.resolve().then(() => service.fetch(new Request(url, {
3345
+ method,
3346
+ headers: request.data.headers,
3347
+ ...method === "GET" || method === "HEAD" || !request.data.body ? {} : { body: request.data.body },
3348
+ signal: controller.signal
3349
+ })));
3350
+ service.track(running);
3351
+ service.track(running.then(async (late) => {
3352
+ if (controller.signal.aborted && late instanceof Response) await late.body?.cancel();
3353
+ }, () => void 0), true);
3354
+ const response = await waitForBrowserOperation(running, controller.signal);
3355
+ if (!(response instanceof Response)) throw new Error("Host Fetch handler did not return a Response.");
3356
+ if (controller.signal.aborted) {
3357
+ try {
3358
+ await response.body?.cancel(controller.signal.reason);
3359
+ } catch (cause) {
3360
+ cleanupFailure = cause;
3361
+ throw cause;
3362
+ }
3363
+ return;
3364
+ }
3365
+ this.send({ type: "stream-start", id: request.id, data: {
3366
+ statusCode: response.status,
3367
+ statusMessage: response.statusText,
3368
+ headers: Object.fromEntries(response.headers)
3369
+ } });
3370
+ if (method === "HEAD" || response.status === 204 || response.status === 205 || response.status === 304 || !response.body) {
3371
+ try {
3372
+ await response.body?.cancel();
3373
+ } catch (cause) {
3374
+ cleanupFailure = cause;
3375
+ throw cause;
3376
+ }
3377
+ this.send({ type: "stream-end", id: request.id });
3378
+ return;
3379
+ }
3380
+ const reader = response.body.getReader();
3381
+ state.reader = reader;
3382
+ let responseBytes = 0;
3383
+ let complete = false;
3384
+ const takeCredit = async () => {
3385
+ if (!state.credit) {
3386
+ await waitForBrowserOperation(new Promise((resolve) => {
3387
+ state.wake = resolve;
3388
+ }), controller.signal);
3389
+ delete state.wake;
3390
+ }
3391
+ if (state.closed || controller.signal.aborted) return false;
3392
+ state.credit = false;
3393
+ return true;
3394
+ };
3395
+ try {
3396
+ while (!state.closed && !controller.signal.aborted) {
3397
+ if (!await takeCredit()) break;
3398
+ const reading = reader.read();
3399
+ service.track(reading);
3400
+ const next = await waitForBrowserOperation(reading, controller.signal);
3401
+ if (next.done) {
3402
+ complete = true;
3403
+ this.send({ type: "stream-end", id: request.id });
3404
+ return;
3405
+ }
3406
+ if (!(next.value instanceof Uint8Array)) throw new Error("Host Fetch-service producer did not yield bytes.");
3407
+ responseBytes += next.value.byteLength;
3408
+ if (responseBytes > this.limits.maxVirtualResponseBytes) throw new Error("Host Fetch-service response exceeds its byte limit.");
3409
+ for (let offset = 0; offset < next.value.byteLength || offset === 0 && next.value.byteLength === 0; offset += HOST_FETCH_MAX_CHUNK_BYTES) {
3410
+ if (offset > 0 && !await takeCredit()) break;
3411
+ if (state.closed || controller.signal.aborted) break;
3412
+ this.send({ type: "stream-chunk", id: request.id, data: {
3413
+ chunkBase64: encodeBase64(next.value.subarray(offset, offset + HOST_FETCH_MAX_CHUNK_BYTES))
3414
+ } });
3415
+ if (next.value.byteLength === 0) break;
3416
+ }
3417
+ }
3418
+ } finally {
3419
+ if (!complete) {
3420
+ try {
3421
+ await reader.cancel();
3422
+ } catch (cause) {
3423
+ cleanupFailure = cause;
3424
+ }
3425
+ }
3426
+ try {
3427
+ reader.releaseLock();
3428
+ } catch (cause) {
3429
+ if (cleanupFailure === void 0) cleanupFailure = cause;
3430
+ }
3431
+ if (cleanupFailure !== void 0) throw cleanupFailure;
3432
+ }
3433
+ } catch (cause) {
3434
+ if (!state.closed && !controller.signal.aborted) {
3435
+ this.send({ type: "stream-error", id: request.id, error: cause instanceof Error ? cause.message : String(cause) });
3436
+ }
3437
+ if (cleanupFailure !== void 0) throw cause;
3438
+ } finally {
3439
+ state.closed = true;
3440
+ service.signal.removeEventListener("abort", abortOwner);
3441
+ this.hostFlowRequests.delete(request.id);
3442
+ }
3443
+ }
3444
+ async handleRequest(message) {
3445
+ this.assertActive();
3446
+ const request = validateSandboxRequest(
3447
+ message,
3448
+ (port) => this.options.bridge.getServerPorts().includes(port),
3449
+ this.limits
3450
+ );
3451
+ if (this.requestControllers.has(request.id)) throw new Error("Duplicate virtual-server request identifier.");
3452
+ const admittedService = this.options.hostFetchServices?.get(request.data.port);
3453
+ const controller = new AbortController();
3454
+ this.requestControllers.set(request.id, { port: request.data.port, controller });
3455
+ const signal = controller.signal;
3456
+ const abortOwner = () => controller.abort(admittedService?.signal.reason);
3457
+ admittedService?.signal.addEventListener("abort", abortOwner, { once: true });
3458
+ if (admittedService?.signal.aborted) abortOwner();
3459
+ let acquired = false;
3460
+ let active = true;
3461
+ try {
3462
+ while (this.activeRequestIds.size >= this.limits.maxVirtualRequests) {
3463
+ await new Promise((resolve, reject) => {
3464
+ const enter = () => {
3465
+ signal.removeEventListener("abort", cancel);
3466
+ resolve();
3467
+ };
3468
+ const cancel = () => {
3469
+ const index = this.waitingRequests.indexOf(enter);
3470
+ if (index >= 0) this.waitingRequests.splice(index, 1);
3471
+ signal.removeEventListener("abort", cancel);
3472
+ reject(signal.reason);
3473
+ };
3474
+ if (signal.aborted) {
3475
+ reject(signal.reason);
3476
+ return;
3477
+ }
3478
+ this.waitingRequests.push(enter);
3479
+ signal.addEventListener("abort", cancel, { once: true });
3480
+ });
3481
+ }
3482
+ signal.throwIfAborted();
3483
+ if (this.options.hostFetchServices?.get(request.data.port) !== admittedService) {
3484
+ throw new Error("Virtual-server registration changed while the request waited.");
3485
+ }
3486
+ this.activeRequestIds.add(request.id);
3487
+ acquired = true;
3488
+ const { method, url, headers, body, streaming } = request.data;
3489
+ if (admittedService) {
3490
+ const operation = this.handleHostFlowRequest(request, controller);
3491
+ admittedService.track(operation, true);
3492
+ await operation;
3493
+ return;
3494
+ }
3495
+ if (request.data.flowControl) throw new Error("Flow-control protocol is not registered for this port.");
3496
+ if (streaming && this.options.bridge.handleStreamingRequest) {
3497
+ let responseBytes = 0;
3498
+ let started = false;
3499
+ let ended = false;
3500
+ let resolveEnd;
3501
+ let rejectEnd;
3502
+ const complete = new Promise((resolve, reject) => {
3503
+ resolveEnd = resolve;
3504
+ rejectEnd = reject;
3505
+ });
3506
+ void complete.catch(() => void 0);
3507
+ const stop = new AbortController();
3508
+ const stopProducer = () => {
3509
+ if (!stop.signal.aborted) stop.abort(signal.reason);
3510
+ };
3511
+ signal.addEventListener("abort", stopProducer, { once: true });
3512
+ const fail = (cause) => {
3513
+ active = false;
3514
+ rejectEnd(cause);
3515
+ stopProducer();
3516
+ };
3517
+ const handler = this.options.bridge.handleStreamingRequest(
3518
+ request.data.port,
3519
+ method,
3520
+ url,
3521
+ headers,
3522
+ body,
3523
+ {
3524
+ start: (statusCode, statusMessage, responseHeaders) => {
3525
+ if (!active || signal.aborted) return;
3526
+ if (started) {
3527
+ fail(new Error("The preview response already started."));
3528
+ return;
3529
+ }
3530
+ started = true;
3531
+ try {
3532
+ this.send({ type: "stream-start", id: request.id, data: { statusCode, statusMessage, headers: responseHeaders } });
3533
+ } catch (cause) {
3534
+ fail(cause);
3535
+ }
3536
+ },
3537
+ chunk: (chunk) => {
3538
+ if (!active || signal.aborted) return;
3539
+ if (!started || ended) {
3540
+ fail(new Error("The preview response is not accepting chunks."));
3541
+ return;
3542
+ }
3543
+ responseBytes += chunk.byteLength;
3544
+ if (responseBytes > this.limits.maxVirtualResponseBytes) {
3545
+ fail(new Error(`Virtual-server streaming response exceeds ${this.limits.maxVirtualResponseBytes} bytes.`));
3546
+ return;
3547
+ }
3548
+ try {
3549
+ this.send({ type: "stream-chunk", id: request.id, data: { chunkBase64: encodeBase64(chunk) } });
3550
+ } catch (cause) {
3551
+ fail(cause);
3552
+ }
3553
+ },
3554
+ end: () => {
3555
+ if (!active || signal.aborted || ended) return;
3556
+ if (!started) {
3557
+ fail(new Error("The preview response ended before its headers."));
3558
+ return;
3559
+ }
3560
+ ended = true;
3561
+ try {
3562
+ this.send({ type: "stream-end", id: request.id });
3563
+ resolveEnd();
3564
+ } catch (cause) {
3565
+ fail(cause);
3566
+ }
3567
+ }
3568
+ },
3569
+ { signal: stop.signal }
3570
+ );
3571
+ const handled = await waitForBrowserOperation(Promise.race([handler, complete.then(() => true)]), signal);
3572
+ if (handled) {
3573
+ await waitForBrowserOperation(complete, signal);
3574
+ return;
3575
+ }
3576
+ if (started || ended || responseBytes > 0) throw new Error("The preview streaming handler wrote a response before declining the request.");
3577
+ }
3578
+ const response = await waitForBrowserOperation(() => this.options.bridge.handleRequest(request.data.port, method, url, headers, body), signal);
3579
+ const rawBody = response.body instanceof Uint8Array ? response.body : new Uint8Array();
3580
+ if (rawBody.byteLength > this.limits.maxVirtualResponseBytes) {
3581
+ throw new Error(`Virtual-server response body exceeds ${this.limits.maxVirtualResponseBytes} bytes.`);
3582
+ }
3583
+ const responseBody = rewriteHtmlModules(rawBody, response.headers, this.options.htmlModuleRewrites);
3584
+ if (responseBody.byteLength > this.limits.maxVirtualResponseBytes) throw new Error("The rewritten preview response exceeds its byte limit.");
3585
+ if (streaming) {
3586
+ this.send({
3587
+ type: "stream-start",
3588
+ id: request.id,
3589
+ data: { statusCode: response.statusCode, statusMessage: response.statusMessage, headers: response.headers }
3590
+ });
3591
+ if (responseBody.length > 0) {
3592
+ this.send({ type: "stream-chunk", id: request.id, data: { chunkBase64: encodeBase64(responseBody) } });
3593
+ }
3594
+ this.send({ type: "stream-end", id: request.id });
3595
+ return;
3596
+ }
3597
+ this.send({
3598
+ type: "response",
3599
+ id: request.id,
3600
+ data: {
3601
+ statusCode: response.statusCode,
3602
+ statusMessage: response.statusMessage,
3603
+ headers: response.headers,
3604
+ bodyBase64: encodeBase64(responseBody)
3605
+ }
3606
+ });
3607
+ } finally {
3608
+ active = false;
3609
+ admittedService?.signal.removeEventListener("abort", abortOwner);
3610
+ this.requestControllers.delete(request.id);
3611
+ if (acquired) {
3612
+ this.activeRequestIds.delete(request.id);
3613
+ this.waitingRequests.shift()?.();
3614
+ }
3615
+ }
3616
+ }
3617
+ async handleWebSocket(message) {
3618
+ await this.socketRelay.handle(message);
3619
+ }
3620
+ };
3621
+ function isInitialPreviewNavigation(message) {
3622
+ if (!message || typeof message !== "object") return false;
3623
+ const request = message;
3624
+ return request.type === "request" && request.data?.method === "GET" && request.data.url === "/" && request.data.headers?.["sec-fetch-dest"] === "iframe";
3625
+ }
3626
+ function validatePreviewObservation(value) {
3627
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
3628
+ const candidate = value;
3629
+ if (!Number.isFinite(candidate.timestamp) || typeof candidate.kind !== "string") return;
3630
+ if (candidate.kind === "console") {
3631
+ if (!(/* @__PURE__ */ new Set(["debug", "info", "log", "warn", "error"])).has(String(candidate.level)) || typeof candidate.message !== "string" || candidate.message.length > 32768) return;
3632
+ return candidate;
3633
+ }
3634
+ if (candidate.kind === "exception" || candidate.kind === "unhandledrejection") {
3635
+ if (typeof candidate.message !== "string" || candidate.message.length > 32768 || candidate.stack !== void 0 && (typeof candidate.stack !== "string" || candidate.stack.length > 65536) || candidate.source !== void 0 && (typeof candidate.source !== "string" || candidate.source.length > 16384)) {
3636
+ return;
3637
+ }
3638
+ return candidate;
3639
+ }
3640
+ if (candidate.kind === "navigation") {
3641
+ if (typeof candidate.url !== "string" || candidate.url.length > 16384 || typeof candidate.title !== "string" || candidate.title.length > 4096) return;
3642
+ return candidate;
3643
+ }
3644
+ }
3645
+
3646
+ // packages/cdp/dist/preview-browser-host.js
3647
+ var CLEAR_TIMEOUT_MS = 15e3;
3648
+ var ORIGINS_RECORD = "browser-substrate.context-origins";
3649
+ var ORIGIN_LOCK = "browser-substrate.context-origin:";
3650
+ var SPARE_ORIGINS = 4;
3651
+ function createPreviewBrowserHost(options) {
3652
+ const sandboxPath = options.sandboxPath ?? "/sandbox.html";
3653
+ const contexts = /* @__PURE__ */ new Map();
3654
+ const emptied = [];
3655
+ const held = /* @__PURE__ */ new Map();
3656
+ const recorded = () => {
3657
+ try {
3658
+ const value = JSON.parse(localStorage.getItem(ORIGINS_RECORD) ?? "[]");
3659
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
3660
+ } catch {
3661
+ return [];
3662
+ }
3663
+ };
3664
+ const record = (origins) => {
3665
+ try {
3666
+ localStorage.setItem(ORIGINS_RECORD, JSON.stringify([...new Set(origins)]));
3667
+ } catch {
3668
+ }
3669
+ };
3670
+ const hold = (origin, ifFree) => new Promise((acquired) => {
3671
+ if (!("locks" in navigator)) {
3672
+ acquired(!ifFree);
3673
+ return;
3674
+ }
3675
+ void navigator.locks.request(`${ORIGIN_LOCK}${origin}`, { ifAvailable: ifFree }, (lock) => {
3676
+ if (!lock) {
3677
+ acquired(false);
3678
+ return void 0;
3679
+ }
3680
+ acquired(true);
3681
+ return new Promise((release2) => held.set(origin, release2));
3682
+ });
3683
+ });
3684
+ const retire = (origin) => clearOrigin(origin).then(() => {
3685
+ record(recorded().filter((item) => item !== origin));
3686
+ }, () => void 0).finally(() => {
3687
+ held.get(origin)?.();
3688
+ held.delete(origin);
3689
+ });
3690
+ const release = (origin) => {
3691
+ if (emptied.length >= SPARE_ORIGINS) {
3692
+ void retire(origin);
3693
+ return;
3694
+ }
3695
+ emptied.push({ origin, ready: clearOrigin(origin, "reuse").then(() => true, () => false) });
3696
+ };
3697
+ let adopting = Promise.resolve();
3698
+ for (const origin of recorded()) {
3699
+ adopting = adopting.then(async () => {
3700
+ if (await hold(origin, true))
3701
+ release(origin);
3702
+ });
3703
+ }
3704
+ const pages = /* @__PURE__ */ new Map();
3705
+ const contextOrigin = () => {
3706
+ const label = `c-${[...crypto.getRandomValues(new Uint8Array(8))].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
3707
+ if (options.contextOrigin)
3708
+ return new URL(options.contextOrigin(label)).origin;
3709
+ const base = new URL(options.sandboxOrigin);
3710
+ if (/^[0-9.]+$/u.test(base.hostname) || base.hostname.startsWith("[")) {
3711
+ throw new Error(`Isolated browser contexts need a sandbox host name; ${base.hostname} is an address.`);
3712
+ }
3713
+ if (base.hostname.endsWith(".workers.dev")) {
3714
+ throw new Error(`Isolated browser contexts need the sandbox on a zone of its own; ${base.hostname} is a workers.dev host.`);
3715
+ }
3716
+ return `${base.protocol}//${label}.${base.host}`;
3717
+ };
3718
+ const frameFor = (origin, query) => {
3719
+ const frame2 = document.createElement("iframe");
3720
+ frame2.style.cssText = "border:0;width:1280px;height:720px;display:block";
3721
+ const url = new URL(sandboxPath, origin);
3722
+ for (const [key, value] of Object.entries(query))
3723
+ url.searchParams.set(key, value);
3724
+ frame2.src = url.href;
3725
+ options.mount.append(frame2);
3726
+ return frame2;
3727
+ };
3728
+ const closePage = async (id) => {
3729
+ const page = pages.get(id);
3730
+ if (!page)
3731
+ return;
3732
+ pages.delete(id);
3733
+ page.bridge.dispose();
3734
+ page.frame.remove();
3735
+ };
3736
+ const clearOrigin = async (origin, clear = "1") => {
3737
+ const nonce = crypto.randomUUID();
3738
+ const frame2 = frameFor(origin, { parentOrigin: location.origin, nonce, clear });
3739
+ try {
3740
+ await new Promise((resolve, reject) => {
3741
+ const timer = setTimeout(() => {
3742
+ cleanup();
3743
+ reject(new Error(`Clearing ${origin} timed out.`));
3744
+ }, CLEAR_TIMEOUT_MS);
3745
+ const receive = (event) => {
3746
+ if (event.origin !== origin || event.source !== frame2.contentWindow)
3747
+ return;
3748
+ const data = event.data;
3749
+ if (data?.channel !== SANDBOX_CHANNEL || data.nonce !== nonce)
3750
+ return;
3751
+ if (data.type === "cleared") {
3752
+ cleanup();
3753
+ resolve();
3754
+ } else if (data.type === "error") {
3755
+ cleanup();
3756
+ reject(new Error(data.payload?.message ?? `Clearing ${origin} failed.`));
3757
+ }
3758
+ };
3759
+ const cleanup = () => {
3760
+ clearTimeout(timer);
3761
+ window.removeEventListener("message", receive);
3762
+ };
3763
+ window.addEventListener("message", receive);
3764
+ });
3765
+ } finally {
3766
+ frame2.remove();
3767
+ }
3768
+ };
3769
+ const contextHost = {
3770
+ async createContext(context, signal) {
3771
+ if (context.proxyServer !== void 0 || context.proxyBypassList !== void 0) {
3772
+ throw new Error("A context's network is the tab's; a per-context proxy is unavailable.");
3773
+ }
3774
+ if (context.originsWithUniversalNetworkAccess?.length) {
3775
+ throw new Error("Universal network access for named origins is unavailable in the tab.");
3776
+ }
3777
+ signal.throwIfAborted();
3778
+ let origin;
3779
+ while (!origin && emptied.length > 0) {
3780
+ const candidate = emptied.shift();
3781
+ if (await candidate.ready)
3782
+ origin = candidate.origin;
3783
+ else {
3784
+ held.get(candidate.origin)?.();
3785
+ held.delete(candidate.origin);
3786
+ }
3787
+ signal.throwIfAborted();
3788
+ }
3789
+ if (!origin) {
3790
+ origin = contextOrigin();
3791
+ await hold(origin, false);
3792
+ record([...recorded(), origin]);
3793
+ }
3794
+ contexts.set(context.id, { origin });
3795
+ },
3796
+ async disposeContext(id) {
3797
+ const context = contexts.get(id);
3798
+ if (!context)
3799
+ return;
3800
+ await Promise.all([...pages].filter(([, page]) => page.contextId === id).map(([pageId]) => closePage(pageId)));
3801
+ contexts.delete(id);
3802
+ release(context.origin);
3803
+ }
3804
+ };
3805
+ const pageHost = {
3806
+ async createPage(page, signal) {
3807
+ const context = page.browserContextId === void 0 ? void 0 : contexts.get(page.browserContextId);
3808
+ if (page.browserContextId !== void 0 && !context)
3809
+ throw new Error("No browser context with given id");
3810
+ const origin = context?.origin ?? new URL(options.sandboxOrigin).origin;
3811
+ const nonce = crypto.randomUUID();
3812
+ const opening = guestAddress(page.url);
3813
+ const port = 0;
3814
+ const frame2 = frameFor(origin, { parentOrigin: location.origin, nonce, port: String(port) });
3815
+ const bridge = new BrowserSandboxBridge({ ...options.bridge, preview: frame2, origin, nonce, port });
3816
+ pages.set(page.id, { frame: frame2, bridge, ...page.browserContextId !== void 0 ? { contextId: page.browserContextId } : {} });
3817
+ const abort = () => {
3818
+ void closePage(page.id);
3819
+ };
3820
+ signal.addEventListener("abort", abort, { once: true });
3821
+ try {
3822
+ const channel = await bridge.connectProtocol(page.id);
3823
+ const transport = new MessagePortTransport(channel.port);
3824
+ void channel.closed.then((reason) => transport.close(1001, reason));
3825
+ await bridge.initialPreviewReady;
3826
+ signal.throwIfAborted();
3827
+ await options.attachSurface(transport, page.id, signal);
3828
+ if (opening)
3829
+ await bridge.navigate(`${origin}/__virtual__/${opening.port}${opening.path}`);
3830
+ } catch (cause) {
3831
+ await closePage(page.id);
3832
+ throw cause;
3833
+ } finally {
3834
+ signal.removeEventListener("abort", abort);
3835
+ }
3836
+ },
3837
+ closePage
3838
+ };
3839
+ return {
3840
+ contextHost,
3841
+ pageHost,
3842
+ async dispose() {
3843
+ await Promise.allSettled([...pages.keys()].map(closePage));
3844
+ const origins = [...contexts.values()].map((context) => context.origin);
3845
+ contexts.clear();
3846
+ await adopting;
3847
+ for (const entry of emptied.splice(0))
3848
+ if (await entry.ready)
3849
+ origins.push(entry.origin);
3850
+ await Promise.allSettled(origins.map(retire));
3851
+ }
3852
+ };
3853
+ }
3854
+ function guestAddress(url) {
3855
+ if (url === "" || url === "about:blank")
3856
+ return void 0;
3857
+ let parsed;
3858
+ try {
3859
+ parsed = new URL(url);
3860
+ } catch {
3861
+ throw new Error(`Invalid page URL: ${url}`);
3862
+ }
3863
+ const loopback = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
3864
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || !loopback) {
3865
+ throw new Error(`A page opens on a guest server of the tab; ${url} is not one.`);
3866
+ }
3867
+ const port = Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));
3868
+ return { port, path: `${parsed.pathname}${parsed.search}${parsed.hash}` };
3869
+ }
3870
+
3871
+ // packages/cdp/dist/preview-cdp-endpoint.js
3872
+ var REATTACH_DELAY_MS = 250;
3873
+ function publishPreviewCdp(options) {
3874
+ const { bridge, port } = options;
3875
+ let endpoint;
3876
+ const host = options.browser ? createPreviewBrowserHost({
3877
+ ...options.browser,
3878
+ attachSurface: (peer, id, signal) => attachAndAwait(endpoint, peer, id, signal)
3879
+ }) : void 0;
3880
+ endpoint = createSocketEndpoint({
3881
+ address: `127.0.0.1:${port}`,
3882
+ ...host ? { contextHost: host.contextHost, pageHost: host.pageHost } : {},
3883
+ ...options.downloads ? { downloadHost: { write: (_context, path, bytes) => options.downloads.write(path, bytes) } } : {},
3884
+ ...options.files ? { fileHost: options.files } : {}
3885
+ });
3886
+ const unlisten = options.listen(port, (socket) => endpoint.serve(socket));
3887
+ const unpublish = options.portTable ? servePortTable(endpoint, options.portTable) : options.discoveryTable ? serveDiscovery(endpoint, options.discoveryTable) : void 0;
3888
+ const targetId = options.targetId ?? "preview";
3889
+ let closed = false;
3890
+ if (bridge)
3891
+ void (async () => {
3892
+ while (!closed) {
3893
+ try {
3894
+ const channel = await bridge.connectProtocol(targetId);
3895
+ const transport = new MessagePortTransport(channel.port);
3896
+ void channel.closed.then((reason) => transport.close(1001, reason));
3897
+ if (closed) {
3898
+ transport.close(1001, "Preview CDP publication closed");
3899
+ return;
3900
+ }
3901
+ const lost = new Promise((resolve) => transport.on("close", resolve));
3902
+ endpoint.attachSurface(transport);
3903
+ await lost;
3904
+ } catch (error) {
3905
+ if (error instanceof Error && error.message === "Browser sandbox bridge is disposed.")
3906
+ return;
3907
+ }
3908
+ if (!closed)
3909
+ await new Promise((resolve) => setTimeout(resolve, REATTACH_DELAY_MS));
3910
+ }
3911
+ })();
3912
+ return {
3913
+ port,
3914
+ url: `http://127.0.0.1:${port}`,
3915
+ attachClient: (peer) => endpoint.attachClient(peer),
3916
+ async close() {
3917
+ if (closed)
3918
+ return;
3919
+ closed = true;
3920
+ unlisten();
3921
+ unpublish?.();
3922
+ await endpoint.close();
3923
+ await host?.dispose();
3924
+ }
3925
+ };
3926
+ }
3927
+ async function attachAndAwait(endpoint, peer, id, signal) {
3928
+ let lost = false;
3929
+ peer.on("close", () => {
3930
+ lost = true;
3931
+ });
3932
+ endpoint.attachSurface(peer);
3933
+ while (!endpoint.targets().some((target) => target.id === id)) {
3934
+ signal.throwIfAborted();
3935
+ if (lost)
3936
+ throw new Error("The page surface closed before it registered.");
3937
+ await new Promise((resolve) => setTimeout(resolve, 10));
3938
+ }
3939
+ }
3940
+ function serveDiscovery(endpoint, table) {
3941
+ const encoder5 = new TextEncoder();
3942
+ table.ports.registerServer({
3943
+ listening: true,
3944
+ address: () => ({ port: table.port, address: "0.0.0.0", family: "IPv4" }),
3945
+ async handleRequest(method, url, headers) {
3946
+ if (method !== "GET" && method !== "HEAD")
3947
+ return { statusCode: 405, headers: { allow: "GET, HEAD" } };
3948
+ const host = headers.host ?? headers.Host ?? `127.0.0.1:${table.port}`;
3949
+ const loopback = /^(localhost|127\.0\.0\.1|\[::1\])(:|$)/u.test(host);
3950
+ const { status, body } = endpoint.discovery(new URL(url, "http://localhost").pathname, loopback ? host : `wss://${host}`);
3951
+ return { statusCode: status, headers: { "content-type": "application/json; charset=UTF-8" }, ...method === "GET" ? { body: encoder5.encode(JSON.stringify(body)) } : {} };
3952
+ }
3953
+ }, table.port);
3954
+ return () => table.ports.unregisterServer(table.port);
3955
+ }
3956
+ function servePortTable(endpoint, table) {
3957
+ const unregisterDiscovery = serveDiscovery(endpoint, table);
3958
+ const unregisterSockets = table.webSockets.register(table.port, {
3959
+ open(path, _protocols, handlers) {
3960
+ const messageListeners = [];
3961
+ const closeListeners = [];
3962
+ let state = 1;
3963
+ const finish = () => {
3964
+ if (state === 3)
3965
+ return;
3966
+ state = 3;
3967
+ for (const listener of closeListeners)
3968
+ listener();
3969
+ };
3970
+ const peer = {
3971
+ get readyState() {
3972
+ return state;
3973
+ },
3974
+ send: (data) => {
3975
+ if (state === 1)
3976
+ handlers.message({ kind: "text", text: data });
3977
+ },
3978
+ close: (code, reason) => {
3979
+ if (state !== 1)
3980
+ return;
3981
+ handlers.close(code, reason, true);
3982
+ finish();
3983
+ },
3984
+ on: ((event, listener) => {
3985
+ if (event === "message")
3986
+ messageListeners.push(listener);
3987
+ else
3988
+ closeListeners.push(listener);
3989
+ })
3990
+ };
3991
+ queueMicrotask(() => {
3992
+ handlers.open("");
3993
+ endpoint.connect(peer, path);
3994
+ });
3995
+ return {
3996
+ send(data) {
3997
+ if (data.kind !== "text") {
3998
+ peer.close(1003, "CDP messages are text");
3999
+ return;
4000
+ }
4001
+ for (const listener of messageListeners)
4002
+ listener(data.text);
4003
+ },
4004
+ close(code = 1e3, reason = "") {
4005
+ handlers.close(code, reason, true);
4006
+ finish();
4007
+ }
4008
+ };
4009
+ }
4010
+ });
4011
+ return () => {
4012
+ unregisterSockets();
4013
+ unregisterDiscovery();
4014
+ };
4015
+ }
4016
+
4017
+ // packages/cdp/dist/tab-chromium.js
4018
+ var TAB_CDP_PORT = 9222;
4019
+ function installTabChromium(options) {
4020
+ const { runtime } = options;
4021
+ const fs = runtime.filesystem;
4022
+ const publication = publishPreviewCdp({
4023
+ ...options.bridge ? { bridge: options.bridge } : {},
4024
+ port: options.port ?? TAB_CDP_PORT,
4025
+ listen: options.listen,
4026
+ browser: options.browser,
4027
+ ...options.portTable ? { portTable: options.portTable } : {},
4028
+ ...options.discoveryTable ? { discoveryTable: options.discoveryTable } : {},
4029
+ downloads: {
4030
+ async write(path, bytes) {
4031
+ fs.mkdirSync(path.slice(0, path.lastIndexOf("/")) || "/", { recursive: true });
4032
+ fs.writeFileSync(path, bytes);
4033
+ }
4034
+ },
4035
+ files: {
4036
+ async read(path) {
4037
+ const stat = fs.statSync(path);
4038
+ if (!stat.isFile())
4039
+ throw new Error(`Not a file: ${path}`);
4040
+ const name = path.slice(path.lastIndexOf("/") + 1);
4041
+ return { data: await fs.readFile(path), name, type: mimeTypeOf(name), lastModified: Math.round(stat.mtimeMs) };
4042
+ },
4043
+ async list(path) {
4044
+ if (!fs.statSync(path).isDirectory())
4045
+ return void 0;
4046
+ const files = [];
4047
+ const walk = (directory, prefix) => {
4048
+ for (const name of fs.readdirSync(directory).map(String).sort()) {
4049
+ const entry = `${directory}/${name}`;
4050
+ if (fs.statSync(entry).isDirectory())
4051
+ walk(entry, `${prefix}${name}/`);
4052
+ else
4053
+ files.push(`${prefix}${name}`);
4054
+ }
4055
+ };
4056
+ walk(path.replace(/\/+$/u, ""), "");
4057
+ return files;
4058
+ }
4059
+ }
4060
+ });
4061
+ const face = chromiumFaceRunner((peer) => publication.attachClient(peer));
4062
+ for (const name of runtime.baselines.find((row) => row.provider.name === "chromium")?.commands ?? [])
4063
+ runtime.registerProgram(name, face, { descriptors: true });
4064
+ const stopPlacing = keepFacePlaced(runtime, options.home, options.roots);
4065
+ return {
4066
+ publication,
4067
+ async dispose() {
4068
+ stopPlacing();
4069
+ await publication.close();
4070
+ }
4071
+ };
4072
+ }
4073
+ function installUnavailableTabChromium(options) {
4074
+ const message = `chromium: ${options.reason}
4075
+ `;
4076
+ const refuse = async (_argv, run) => {
4077
+ run.onStderr?.(message);
4078
+ return { stdout: "", stderr: message, exitCode: 1 };
4079
+ };
4080
+ for (const name of options.runtime.baselines.find((row) => row.provider.name === "chromium")?.commands ?? [])
4081
+ options.runtime.registerProgram(name, refuse, { descriptors: true });
4082
+ return keepFacePlaced(options.runtime, options.home, options.roots);
4083
+ }
4084
+ function keepFacePlaced(runtime, home, roots) {
4085
+ const fs = runtime.filesystem;
4086
+ const baseline = runtime.baselines.find((row) => row.provider.name === "chromium")?.chromium;
4087
+ if (!baseline)
4088
+ return () => void 0;
4089
+ let placed = "";
4090
+ const place = async () => {
4091
+ const revisions = (await playwrightChromiumRevisions(fs, roots)).sort().join(",");
4092
+ if (revisions === placed)
4093
+ return;
4094
+ placed = revisions;
4095
+ await placeChromiumFace(fs, `${home}/.cache/ms-playwright`, revisions ? revisions.split(",") : [], baseline);
4096
+ };
4097
+ const placing = setInterval(() => {
4098
+ void place().catch((cause) => console.error("Chromium face placement failed:", cause));
4099
+ }, 2e3);
4100
+ void place().catch((cause) => console.error("Chromium face placement failed:", cause));
4101
+ return () => clearInterval(placing);
4102
+ }
4103
+ function mimeTypeOf(name) {
4104
+ const extension = name.slice(name.lastIndexOf(".") + 1).toLowerCase();
4105
+ return {
4106
+ png: "image/png",
4107
+ jpg: "image/jpeg",
4108
+ jpeg: "image/jpeg",
4109
+ gif: "image/gif",
4110
+ webp: "image/webp",
4111
+ svg: "image/svg+xml",
4112
+ ico: "image/x-icon",
4113
+ bmp: "image/bmp",
4114
+ avif: "image/avif",
4115
+ txt: "text/plain",
4116
+ html: "text/html",
4117
+ htm: "text/html",
4118
+ css: "text/css",
4119
+ js: "text/javascript",
4120
+ mjs: "text/javascript",
4121
+ json: "application/json",
4122
+ xml: "text/xml",
4123
+ csv: "text/csv",
4124
+ pdf: "application/pdf",
4125
+ zip: "application/zip",
4126
+ gz: "application/gzip",
4127
+ wasm: "application/wasm",
4128
+ mp3: "audio/mpeg",
4129
+ wav: "audio/wav",
4130
+ ogg: "audio/ogg",
4131
+ mp4: "video/mp4",
4132
+ webm: "video/webm",
4133
+ woff: "font/woff",
4134
+ woff2: "font/woff2"
4135
+ }[extension] ?? "";
4136
+ }
4137
+ export {
4138
+ TAB_CDP_PORT,
4139
+ installTabChromium,
4140
+ installUnavailableTabChromium
4141
+ };