@heojeongbo/wirestage 0.0.0-stage → 0.1.1

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 (489) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSES.md +21 -0
  3. package/README.md +150 -2
  4. package/dist/app/playwright/index.d.ts +3 -0
  5. package/dist/app/playwright/index.d.ts.map +1 -0
  6. package/dist/app/playwright/index.js +2 -0
  7. package/dist/app/playwright/index.js.map +1 -0
  8. package/dist/app/playwright/lib/fixture.d.ts +61 -0
  9. package/dist/app/playwright/lib/fixture.d.ts.map +1 -0
  10. package/dist/app/playwright/lib/fixture.js +110 -0
  11. package/dist/app/playwright/lib/fixture.js.map +1 -0
  12. package/dist/connect.d.ts +7 -0
  13. package/dist/connect.d.ts.map +1 -0
  14. package/dist/connect.js +7 -0
  15. package/dist/connect.js.map +1 -0
  16. package/dist/entities/frame/@x/plugin.d.ts +9 -0
  17. package/dist/entities/frame/@x/plugin.d.ts.map +1 -0
  18. package/dist/entities/frame/@x/plugin.js +2 -0
  19. package/dist/entities/frame/@x/plugin.js.map +1 -0
  20. package/dist/entities/frame/index.d.ts +6 -0
  21. package/dist/entities/frame/index.d.ts.map +1 -0
  22. package/dist/entities/frame/index.js +4 -0
  23. package/dist/entities/frame/index.js.map +1 -0
  24. package/dist/entities/frame/lib/filter.d.ts +8 -0
  25. package/dist/entities/frame/lib/filter.d.ts.map +1 -0
  26. package/dist/entities/frame/lib/filter.js +62 -0
  27. package/dist/entities/frame/lib/filter.js.map +1 -0
  28. package/dist/entities/frame/lib/payload.d.ts +8 -0
  29. package/dist/entities/frame/lib/payload.d.ts.map +1 -0
  30. package/dist/entities/frame/lib/payload.js +42 -0
  31. package/dist/entities/frame/lib/payload.js.map +1 -0
  32. package/dist/entities/frame/lib/snapshot.d.ts +15 -0
  33. package/dist/entities/frame/lib/snapshot.d.ts.map +1 -0
  34. package/dist/entities/frame/lib/snapshot.js +90 -0
  35. package/dist/entities/frame/lib/snapshot.js.map +1 -0
  36. package/dist/entities/frame/lib/timeline.d.ts +23 -0
  37. package/dist/entities/frame/lib/timeline.d.ts.map +1 -0
  38. package/dist/entities/frame/lib/timeline.js +122 -0
  39. package/dist/entities/frame/lib/timeline.js.map +1 -0
  40. package/dist/entities/frame/model/frame.d.ts +182 -0
  41. package/dist/entities/frame/model/frame.d.ts.map +1 -0
  42. package/dist/entities/frame/model/frame.js +2 -0
  43. package/dist/entities/frame/model/frame.js.map +1 -0
  44. package/dist/entities/plugin/index.d.ts +9 -0
  45. package/dist/entities/plugin/index.d.ts.map +1 -0
  46. package/dist/entities/plugin/index.js +3 -0
  47. package/dist/entities/plugin/index.js.map +1 -0
  48. package/dist/entities/plugin/lib/define-plugin.d.ts +8 -0
  49. package/dist/entities/plugin/lib/define-plugin.d.ts.map +1 -0
  50. package/dist/entities/plugin/lib/define-plugin.js +9 -0
  51. package/dist/entities/plugin/lib/define-plugin.js.map +1 -0
  52. package/dist/entities/plugin/lib/named.d.ts +10 -0
  53. package/dist/entities/plugin/lib/named.d.ts.map +1 -0
  54. package/dist/entities/plugin/lib/named.js +17 -0
  55. package/dist/entities/plugin/lib/named.js.map +1 -0
  56. package/dist/entities/plugin/model/attachment.d.ts +18 -0
  57. package/dist/entities/plugin/model/attachment.d.ts.map +1 -0
  58. package/dist/entities/plugin/model/attachment.js +2 -0
  59. package/dist/entities/plugin/model/attachment.js.map +1 -0
  60. package/dist/entities/plugin/model/http.d.ts +135 -0
  61. package/dist/entities/plugin/model/http.d.ts.map +1 -0
  62. package/dist/entities/plugin/model/http.js +2 -0
  63. package/dist/entities/plugin/model/http.js.map +1 -0
  64. package/dist/entities/plugin/model/page.d.ts +100 -0
  65. package/dist/entities/plugin/model/page.d.ts.map +1 -0
  66. package/dist/entities/plugin/model/page.js +2 -0
  67. package/dist/entities/plugin/model/page.js.map +1 -0
  68. package/dist/entities/plugin/model/plugin.d.ts +83 -0
  69. package/dist/entities/plugin/model/plugin.d.ts.map +1 -0
  70. package/dist/entities/plugin/model/plugin.js +2 -0
  71. package/dist/entities/plugin/model/plugin.js.map +1 -0
  72. package/dist/entities/plugin/model/socket.d.ts +107 -0
  73. package/dist/entities/plugin/model/socket.d.ts.map +1 -0
  74. package/dist/entities/plugin/model/socket.js +2 -0
  75. package/dist/entities/plugin/model/socket.js.map +1 -0
  76. package/dist/entities/plugin/model/stream.d.ts +46 -0
  77. package/dist/entities/plugin/model/stream.d.ts.map +1 -0
  78. package/dist/entities/plugin/model/stream.js +2 -0
  79. package/dist/entities/plugin/model/stream.js.map +1 -0
  80. package/dist/features/connect/index.d.ts +5 -0
  81. package/dist/features/connect/index.d.ts.map +1 -0
  82. package/dist/features/connect/index.js +4 -0
  83. package/dist/features/connect/index.js.map +1 -0
  84. package/dist/features/connect/lib/calls.d.ts +9 -0
  85. package/dist/features/connect/lib/calls.d.ts.map +1 -0
  86. package/dist/features/connect/lib/calls.js +46 -0
  87. package/dist/features/connect/lib/calls.js.map +1 -0
  88. package/dist/features/connect/lib/client.d.ts +33 -0
  89. package/dist/features/connect/lib/client.d.ts.map +1 -0
  90. package/dist/features/connect/lib/client.js +147 -0
  91. package/dist/features/connect/lib/client.js.map +1 -0
  92. package/dist/features/connect/lib/create-connect.d.ts +14 -0
  93. package/dist/features/connect/lib/create-connect.d.ts.map +1 -0
  94. package/dist/features/connect/lib/create-connect.js +124 -0
  95. package/dist/features/connect/lib/create-connect.js.map +1 -0
  96. package/dist/features/connect/lib/faults.d.ts +18 -0
  97. package/dist/features/connect/lib/faults.d.ts.map +1 -0
  98. package/dist/features/connect/lib/faults.js +85 -0
  99. package/dist/features/connect/lib/faults.js.map +1 -0
  100. package/dist/features/connect/lib/protocol.d.ts +42 -0
  101. package/dist/features/connect/lib/protocol.d.ts.map +1 -0
  102. package/dist/features/connect/lib/protocol.js +228 -0
  103. package/dist/features/connect/lib/protocol.js.map +1 -0
  104. package/dist/features/connect/lib/record.d.ts +14 -0
  105. package/dist/features/connect/lib/record.d.ts.map +1 -0
  106. package/dist/features/connect/lib/record.js +36 -0
  107. package/dist/features/connect/lib/record.js.map +1 -0
  108. package/dist/features/connect/lib/recorder.d.ts +77 -0
  109. package/dist/features/connect/lib/recorder.d.ts.map +1 -0
  110. package/dist/features/connect/lib/recorder.js +209 -0
  111. package/dist/features/connect/lib/recorder.js.map +1 -0
  112. package/dist/features/connect/lib/rules.d.ts +51 -0
  113. package/dist/features/connect/lib/rules.d.ts.map +1 -0
  114. package/dist/features/connect/lib/rules.js +222 -0
  115. package/dist/features/connect/lib/rules.js.map +1 -0
  116. package/dist/features/connect/lib/serve.d.ts +10 -0
  117. package/dist/features/connect/lib/serve.d.ts.map +1 -0
  118. package/dist/features/connect/lib/serve.js +78 -0
  119. package/dist/features/connect/lib/serve.js.map +1 -0
  120. package/dist/features/connect/lib/status-of.d.ts +11 -0
  121. package/dist/features/connect/lib/status-of.d.ts.map +1 -0
  122. package/dist/features/connect/lib/status-of.js +22 -0
  123. package/dist/features/connect/lib/status-of.js.map +1 -0
  124. package/dist/features/connect/lib/stubs.d.ts +18 -0
  125. package/dist/features/connect/lib/stubs.d.ts.map +1 -0
  126. package/dist/features/connect/lib/stubs.js +174 -0
  127. package/dist/features/connect/lib/stubs.js.map +1 -0
  128. package/dist/features/connect/lib/universal.d.ts +19 -0
  129. package/dist/features/connect/lib/universal.d.ts.map +1 -0
  130. package/dist/features/connect/lib/universal.js +71 -0
  131. package/dist/features/connect/lib/universal.js.map +1 -0
  132. package/dist/features/connect/model/types.d.ts +247 -0
  133. package/dist/features/connect/model/types.d.ts.map +1 -0
  134. package/dist/features/connect/model/types.js +2 -0
  135. package/dist/features/connect/model/types.js.map +1 -0
  136. package/dist/features/rest/index.d.ts +5 -0
  137. package/dist/features/rest/index.d.ts.map +1 -0
  138. package/dist/features/rest/index.js +3 -0
  139. package/dist/features/rest/index.js.map +1 -0
  140. package/dist/features/rest/lib/calls.d.ts +8 -0
  141. package/dist/features/rest/lib/calls.d.ts.map +1 -0
  142. package/dist/features/rest/lib/calls.js +32 -0
  143. package/dist/features/rest/lib/calls.js.map +1 -0
  144. package/dist/features/rest/lib/create-rest.d.ts +8 -0
  145. package/dist/features/rest/lib/create-rest.d.ts.map +1 -0
  146. package/dist/features/rest/lib/create-rest.js +136 -0
  147. package/dist/features/rest/lib/create-rest.js.map +1 -0
  148. package/dist/features/rest/lib/path.d.ts +14 -0
  149. package/dist/features/rest/lib/path.d.ts.map +1 -0
  150. package/dist/features/rest/lib/path.js +58 -0
  151. package/dist/features/rest/lib/path.js.map +1 -0
  152. package/dist/features/rest/lib/reply.d.ts +25 -0
  153. package/dist/features/rest/lib/reply.d.ts.map +1 -0
  154. package/dist/features/rest/lib/reply.js +40 -0
  155. package/dist/features/rest/lib/reply.js.map +1 -0
  156. package/dist/features/rest/model/types.d.ts +74 -0
  157. package/dist/features/rest/model/types.d.ts.map +1 -0
  158. package/dist/features/rest/model/types.js +2 -0
  159. package/dist/features/rest/model/types.js.map +1 -0
  160. package/dist/features/sse/index.d.ts +4 -0
  161. package/dist/features/sse/index.d.ts.map +1 -0
  162. package/dist/features/sse/index.js +3 -0
  163. package/dist/features/sse/index.js.map +1 -0
  164. package/dist/features/sse/lib/create-sse.d.ts +9 -0
  165. package/dist/features/sse/lib/create-sse.d.ts.map +1 -0
  166. package/dist/features/sse/lib/create-sse.js +121 -0
  167. package/dist/features/sse/lib/create-sse.js.map +1 -0
  168. package/dist/features/sse/lib/encode.d.ts +8 -0
  169. package/dist/features/sse/lib/encode.d.ts.map +1 -0
  170. package/dist/features/sse/lib/encode.js +29 -0
  171. package/dist/features/sse/lib/encode.js.map +1 -0
  172. package/dist/features/sse/model/types.d.ts +68 -0
  173. package/dist/features/sse/model/types.d.ts.map +1 -0
  174. package/dist/features/sse/model/types.js +2 -0
  175. package/dist/features/sse/model/types.js.map +1 -0
  176. package/dist/features/webrtc/index.d.ts +3 -0
  177. package/dist/features/webrtc/index.d.ts.map +1 -0
  178. package/dist/features/webrtc/index.js +2 -0
  179. package/dist/features/webrtc/index.js.map +1 -0
  180. package/dist/features/webrtc/lib/create-webrtc.d.ts +9 -0
  181. package/dist/features/webrtc/lib/create-webrtc.d.ts.map +1 -0
  182. package/dist/features/webrtc/lib/create-webrtc.js +123 -0
  183. package/dist/features/webrtc/lib/create-webrtc.js.map +1 -0
  184. package/dist/features/webrtc/lib/rtc-page-module.d.ts +22 -0
  185. package/dist/features/webrtc/lib/rtc-page-module.d.ts.map +1 -0
  186. package/dist/features/webrtc/lib/rtc-page-module.js +161 -0
  187. package/dist/features/webrtc/lib/rtc-page-module.js.map +1 -0
  188. package/dist/features/webrtc/lib/to-frame.d.ts +9 -0
  189. package/dist/features/webrtc/lib/to-frame.d.ts.map +1 -0
  190. package/dist/features/webrtc/lib/to-frame.js +43 -0
  191. package/dist/features/webrtc/lib/to-frame.js.map +1 -0
  192. package/dist/features/webrtc/model/types.d.ts +70 -0
  193. package/dist/features/webrtc/model/types.d.ts.map +1 -0
  194. package/dist/features/webrtc/model/types.js +2 -0
  195. package/dist/features/webrtc/model/types.js.map +1 -0
  196. package/dist/features/websocket/index.d.ts +3 -0
  197. package/dist/features/websocket/index.d.ts.map +1 -0
  198. package/dist/features/websocket/index.js +2 -0
  199. package/dist/features/websocket/index.js.map +1 -0
  200. package/dist/features/websocket/lib/create-websocket.d.ts +9 -0
  201. package/dist/features/websocket/lib/create-websocket.d.ts.map +1 -0
  202. package/dist/features/websocket/lib/create-websocket.js +34 -0
  203. package/dist/features/websocket/lib/create-websocket.js.map +1 -0
  204. package/dist/features/websocket/lib/mock.d.ts +6 -0
  205. package/dist/features/websocket/lib/mock.d.ts.map +1 -0
  206. package/dist/features/websocket/lib/mock.js +96 -0
  207. package/dist/features/websocket/lib/mock.js.map +1 -0
  208. package/dist/features/websocket/lib/proxy.d.ts +9 -0
  209. package/dist/features/websocket/lib/proxy.d.ts.map +1 -0
  210. package/dist/features/websocket/lib/proxy.js +120 -0
  211. package/dist/features/websocket/lib/proxy.js.map +1 -0
  212. package/dist/features/websocket/lib/wire.d.ts +16 -0
  213. package/dist/features/websocket/lib/wire.d.ts.map +1 -0
  214. package/dist/features/websocket/lib/wire.js +46 -0
  215. package/dist/features/websocket/lib/wire.js.map +1 -0
  216. package/dist/features/websocket/model/types.d.ts +133 -0
  217. package/dist/features/websocket/model/types.d.ts.map +1 -0
  218. package/dist/features/websocket/model/types.js +2 -0
  219. package/dist/features/websocket/model/types.js.map +1 -0
  220. package/dist/index.d.ts +27 -0
  221. package/dist/index.d.ts.map +1 -0
  222. package/dist/index.js +16 -0
  223. package/dist/index.js.map +1 -0
  224. package/dist/playwright.d.ts +4 -0
  225. package/dist/playwright.d.ts.map +1 -0
  226. package/dist/playwright.js +2 -0
  227. package/dist/playwright.js.map +1 -0
  228. package/dist/rest.d.ts +3 -0
  229. package/dist/rest.d.ts.map +1 -0
  230. package/dist/rest.js +3 -0
  231. package/dist/rest.js.map +1 -0
  232. package/dist/shared/api/http-server/index.d.ts +3 -0
  233. package/dist/shared/api/http-server/index.d.ts.map +1 -0
  234. package/dist/shared/api/http-server/index.js +2 -0
  235. package/dist/shared/api/http-server/index.js.map +1 -0
  236. package/dist/shared/api/http-server/listen.d.ts +25 -0
  237. package/dist/shared/api/http-server/listen.d.ts.map +1 -0
  238. package/dist/shared/api/http-server/listen.js +44 -0
  239. package/dist/shared/api/http-server/listen.js.map +1 -0
  240. package/dist/shared/api/playwright/index.d.ts +2 -0
  241. package/dist/shared/api/playwright/index.d.ts.map +1 -0
  242. package/dist/shared/api/playwright/index.js +2 -0
  243. package/dist/shared/api/playwright/index.js.map +1 -0
  244. package/dist/shared/api/playwright/ports.d.ts +126 -0
  245. package/dist/shared/api/playwright/ports.d.ts.map +1 -0
  246. package/dist/shared/api/playwright/ports.js +11 -0
  247. package/dist/shared/api/playwright/ports.js.map +1 -0
  248. package/dist/shared/api/stream-server/index.d.ts +3 -0
  249. package/dist/shared/api/stream-server/index.d.ts.map +1 -0
  250. package/dist/shared/api/stream-server/index.js +2 -0
  251. package/dist/shared/api/stream-server/index.js.map +1 -0
  252. package/dist/shared/api/stream-server/stream-server.d.ts +45 -0
  253. package/dist/shared/api/stream-server/stream-server.d.ts.map +1 -0
  254. package/dist/shared/api/stream-server/stream-server.js +167 -0
  255. package/dist/shared/api/stream-server/stream-server.js.map +1 -0
  256. package/dist/shared/lib/async/arrivals.d.ts +31 -0
  257. package/dist/shared/lib/async/arrivals.d.ts.map +1 -0
  258. package/dist/shared/lib/async/arrivals.js +57 -0
  259. package/dist/shared/lib/async/arrivals.js.map +1 -0
  260. package/dist/shared/lib/async/async-queue.d.ts +23 -0
  261. package/dist/shared/lib/async/async-queue.d.ts.map +1 -0
  262. package/dist/shared/lib/async/async-queue.js +80 -0
  263. package/dist/shared/lib/async/async-queue.js.map +1 -0
  264. package/dist/shared/lib/async/deferred.d.ts +12 -0
  265. package/dist/shared/lib/async/deferred.d.ts.map +1 -0
  266. package/dist/shared/lib/async/deferred.js +34 -0
  267. package/dist/shared/lib/async/deferred.js.map +1 -0
  268. package/dist/shared/lib/async/index.d.ts +8 -0
  269. package/dist/shared/lib/async/index.d.ts.map +1 -0
  270. package/dist/shared/lib/async/index.js +5 -0
  271. package/dist/shared/lib/async/index.js.map +1 -0
  272. package/dist/shared/lib/async/run-all.d.ts +8 -0
  273. package/dist/shared/lib/async/run-all.d.ts.map +1 -0
  274. package/dist/shared/lib/async/run-all.js +22 -0
  275. package/dist/shared/lib/async/run-all.js.map +1 -0
  276. package/dist/shared/lib/bytes/bytes.d.ts +18 -0
  277. package/dist/shared/lib/bytes/bytes.d.ts.map +1 -0
  278. package/dist/shared/lib/bytes/bytes.js +48 -0
  279. package/dist/shared/lib/bytes/bytes.js.map +1 -0
  280. package/dist/shared/lib/bytes/index.d.ts +3 -0
  281. package/dist/shared/lib/bytes/index.d.ts.map +1 -0
  282. package/dist/shared/lib/bytes/index.js +2 -0
  283. package/dist/shared/lib/bytes/index.js.map +1 -0
  284. package/dist/shared/lib/clock/clock.d.ts +29 -0
  285. package/dist/shared/lib/clock/clock.d.ts.map +1 -0
  286. package/dist/shared/lib/clock/clock.js +85 -0
  287. package/dist/shared/lib/clock/clock.js.map +1 -0
  288. package/dist/shared/lib/clock/index.d.ts +3 -0
  289. package/dist/shared/lib/clock/index.d.ts.map +1 -0
  290. package/dist/shared/lib/clock/index.js +2 -0
  291. package/dist/shared/lib/clock/index.js.map +1 -0
  292. package/dist/shared/lib/cookies/index.d.ts +3 -0
  293. package/dist/shared/lib/cookies/index.d.ts.map +1 -0
  294. package/dist/shared/lib/cookies/index.js +2 -0
  295. package/dist/shared/lib/cookies/index.js.map +1 -0
  296. package/dist/shared/lib/cookies/set-cookie.d.ts +22 -0
  297. package/dist/shared/lib/cookies/set-cookie.d.ts.map +1 -0
  298. package/dist/shared/lib/cookies/set-cookie.js +89 -0
  299. package/dist/shared/lib/cookies/set-cookie.js.map +1 -0
  300. package/dist/shared/lib/envelope/envelope.d.ts +50 -0
  301. package/dist/shared/lib/envelope/envelope.d.ts.map +1 -0
  302. package/dist/shared/lib/envelope/envelope.js +111 -0
  303. package/dist/shared/lib/envelope/envelope.js.map +1 -0
  304. package/dist/shared/lib/envelope/index.d.ts +3 -0
  305. package/dist/shared/lib/envelope/index.d.ts.map +1 -0
  306. package/dist/shared/lib/envelope/index.js +2 -0
  307. package/dist/shared/lib/envelope/index.js.map +1 -0
  308. package/dist/shared/lib/errors/index.d.ts +3 -0
  309. package/dist/shared/lib/errors/index.d.ts.map +1 -0
  310. package/dist/shared/lib/errors/index.js +2 -0
  311. package/dist/shared/lib/errors/index.js.map +1 -0
  312. package/dist/shared/lib/errors/wirestage-error.d.ts +28 -0
  313. package/dist/shared/lib/errors/wirestage-error.d.ts.map +1 -0
  314. package/dist/shared/lib/errors/wirestage-error.js +16 -0
  315. package/dist/shared/lib/errors/wirestage-error.js.map +1 -0
  316. package/dist/shared/lib/id/id.d.ts +10 -0
  317. package/dist/shared/lib/id/id.d.ts.map +1 -0
  318. package/dist/shared/lib/id/id.js +26 -0
  319. package/dist/shared/lib/id/id.js.map +1 -0
  320. package/dist/shared/lib/id/index.d.ts +2 -0
  321. package/dist/shared/lib/id/index.d.ts.map +1 -0
  322. package/dist/shared/lib/id/index.js +2 -0
  323. package/dist/shared/lib/id/index.js.map +1 -0
  324. package/dist/shared/lib/logger/index.d.ts +3 -0
  325. package/dist/shared/lib/logger/index.d.ts.map +1 -0
  326. package/dist/shared/lib/logger/index.js +2 -0
  327. package/dist/shared/lib/logger/index.js.map +1 -0
  328. package/dist/shared/lib/logger/logger.d.ts +46 -0
  329. package/dist/shared/lib/logger/logger.d.ts.map +1 -0
  330. package/dist/shared/lib/logger/logger.js +67 -0
  331. package/dist/shared/lib/logger/logger.js.map +1 -0
  332. package/dist/shared/lib/match/index.d.ts +3 -0
  333. package/dist/shared/lib/match/index.d.ts.map +1 -0
  334. package/dist/shared/lib/match/index.js +2 -0
  335. package/dist/shared/lib/match/index.js.map +1 -0
  336. package/dist/shared/lib/match/url-matcher.d.ts +27 -0
  337. package/dist/shared/lib/match/url-matcher.d.ts.map +1 -0
  338. package/dist/shared/lib/match/url-matcher.js +88 -0
  339. package/dist/shared/lib/match/url-matcher.js.map +1 -0
  340. package/dist/sse.d.ts +3 -0
  341. package/dist/sse.d.ts.map +1 -0
  342. package/dist/sse.js +3 -0
  343. package/dist/sse.js.map +1 -0
  344. package/dist/webrtc.d.ts +3 -0
  345. package/dist/webrtc.d.ts.map +1 -0
  346. package/dist/webrtc.js +3 -0
  347. package/dist/webrtc.js.map +1 -0
  348. package/dist/websocket.d.ts +3 -0
  349. package/dist/websocket.d.ts.map +1 -0
  350. package/dist/websocket.js +3 -0
  351. package/dist/websocket.js.map +1 -0
  352. package/dist/widgets/stage/index.d.ts +3 -0
  353. package/dist/widgets/stage/index.d.ts.map +1 -0
  354. package/dist/widgets/stage/index.js +2 -0
  355. package/dist/widgets/stage/index.js.map +1 -0
  356. package/dist/widgets/stage/lib/attached-context.d.ts +10 -0
  357. package/dist/widgets/stage/lib/attached-context.d.ts.map +1 -0
  358. package/dist/widgets/stage/lib/attached-context.js +2 -0
  359. package/dist/widgets/stage/lib/attached-context.js.map +1 -0
  360. package/dist/widgets/stage/lib/create-stage.d.ts +12 -0
  361. package/dist/widgets/stage/lib/create-stage.d.ts.map +1 -0
  362. package/dist/widgets/stage/lib/create-stage.js +250 -0
  363. package/dist/widgets/stage/lib/create-stage.js.map +1 -0
  364. package/dist/widgets/stage/lib/http-router.d.ts +44 -0
  365. package/dist/widgets/stage/lib/http-router.d.ts.map +1 -0
  366. package/dist/widgets/stage/lib/http-router.js +696 -0
  367. package/dist/widgets/stage/lib/http-router.js.map +1 -0
  368. package/dist/widgets/stage/lib/page-host.d.ts +36 -0
  369. package/dist/widgets/stage/lib/page-host.d.ts.map +1 -0
  370. package/dist/widgets/stage/lib/page-host.js +240 -0
  371. package/dist/widgets/stage/lib/page-host.js.map +1 -0
  372. package/dist/widgets/stage/lib/page-runtime.d.ts +93 -0
  373. package/dist/widgets/stage/lib/page-runtime.d.ts.map +1 -0
  374. package/dist/widgets/stage/lib/page-runtime.js +247 -0
  375. package/dist/widgets/stage/lib/page-runtime.js.map +1 -0
  376. package/dist/widgets/stage/lib/reads.d.ts +21 -0
  377. package/dist/widgets/stage/lib/reads.d.ts.map +1 -0
  378. package/dist/widgets/stage/lib/reads.js +97 -0
  379. package/dist/widgets/stage/lib/reads.js.map +1 -0
  380. package/dist/widgets/stage/lib/response-stream.d.ts +23 -0
  381. package/dist/widgets/stage/lib/response-stream.d.ts.map +1 -0
  382. package/dist/widgets/stage/lib/response-stream.js +108 -0
  383. package/dist/widgets/stage/lib/response-stream.js.map +1 -0
  384. package/dist/widgets/stage/lib/socket-router.d.ts +25 -0
  385. package/dist/widgets/stage/lib/socket-router.d.ts.map +1 -0
  386. package/dist/widgets/stage/lib/socket-router.js +233 -0
  387. package/dist/widgets/stage/lib/socket-router.js.map +1 -0
  388. package/dist/widgets/stage/model/stage.d.ts +88 -0
  389. package/dist/widgets/stage/model/stage.d.ts.map +1 -0
  390. package/dist/widgets/stage/model/stage.js +2 -0
  391. package/dist/widgets/stage/model/stage.js.map +1 -0
  392. package/package.json +140 -5
  393. package/src/app/playwright/index.ts +7 -0
  394. package/src/app/playwright/lib/fixture.ts +201 -0
  395. package/src/connect.ts +6 -0
  396. package/src/entities/frame/@x/plugin.ts +8 -0
  397. package/src/entities/frame/index.ts +19 -0
  398. package/src/entities/frame/lib/filter.ts +58 -0
  399. package/src/entities/frame/lib/payload.ts +42 -0
  400. package/src/entities/frame/lib/snapshot.ts +105 -0
  401. package/src/entities/frame/lib/timeline.ts +147 -0
  402. package/src/entities/frame/model/frame.ts +191 -0
  403. package/src/entities/plugin/index.ts +36 -0
  404. package/src/entities/plugin/lib/define-plugin.ts +12 -0
  405. package/src/entities/plugin/lib/named.ts +21 -0
  406. package/src/entities/plugin/model/attachment.ts +18 -0
  407. package/src/entities/plugin/model/http.ts +156 -0
  408. package/src/entities/plugin/model/page.ts +110 -0
  409. package/src/entities/plugin/model/plugin.ts +96 -0
  410. package/src/entities/plugin/model/socket.ts +113 -0
  411. package/src/entities/plugin/model/stream.ts +46 -0
  412. package/src/features/connect/index.ts +23 -0
  413. package/src/features/connect/lib/calls.ts +58 -0
  414. package/src/features/connect/lib/client.ts +203 -0
  415. package/src/features/connect/lib/create-connect.ts +164 -0
  416. package/src/features/connect/lib/faults.ts +132 -0
  417. package/src/features/connect/lib/protocol.ts +321 -0
  418. package/src/features/connect/lib/record.ts +48 -0
  419. package/src/features/connect/lib/recorder.ts +306 -0
  420. package/src/features/connect/lib/rules.ts +385 -0
  421. package/src/features/connect/lib/serve.ts +114 -0
  422. package/src/features/connect/lib/status-of.ts +20 -0
  423. package/src/features/connect/lib/stubs.ts +282 -0
  424. package/src/features/connect/lib/universal.ts +100 -0
  425. package/src/features/connect/model/types.ts +305 -0
  426. package/src/features/rest/index.ts +23 -0
  427. package/src/features/rest/lib/calls.ts +31 -0
  428. package/src/features/rest/lib/create-rest.ts +169 -0
  429. package/src/features/rest/lib/path.ts +70 -0
  430. package/src/features/rest/lib/reply.ts +74 -0
  431. package/src/features/rest/model/types.ts +113 -0
  432. package/src/features/sse/index.ts +10 -0
  433. package/src/features/sse/lib/create-sse.ts +129 -0
  434. package/src/features/sse/lib/encode.ts +28 -0
  435. package/src/features/sse/model/types.ts +72 -0
  436. package/src/features/webrtc/index.ts +6 -0
  437. package/src/features/webrtc/lib/create-webrtc.ts +149 -0
  438. package/src/features/webrtc/lib/rtc-page-module.ts +189 -0
  439. package/src/features/webrtc/lib/to-frame.ts +63 -0
  440. package/src/features/webrtc/model/types.ts +80 -0
  441. package/src/features/websocket/index.ts +15 -0
  442. package/src/features/websocket/lib/create-websocket.ts +41 -0
  443. package/src/features/websocket/lib/mock.ts +114 -0
  444. package/src/features/websocket/lib/proxy.ts +153 -0
  445. package/src/features/websocket/lib/wire.ts +78 -0
  446. package/src/features/websocket/model/types.ts +134 -0
  447. package/src/index.ts +111 -0
  448. package/src/playwright.ts +8 -0
  449. package/src/rest.ts +2 -0
  450. package/src/shared/api/http-server/index.ts +2 -0
  451. package/src/shared/api/http-server/listen.ts +71 -0
  452. package/src/shared/api/playwright/index.ts +17 -0
  453. package/src/shared/api/playwright/ports.ts +150 -0
  454. package/src/shared/api/stream-server/index.ts +7 -0
  455. package/src/shared/api/stream-server/stream-server.ts +254 -0
  456. package/src/shared/lib/async/arrivals.ts +91 -0
  457. package/src/shared/lib/async/async-queue.ts +101 -0
  458. package/src/shared/lib/async/deferred.ts +38 -0
  459. package/src/shared/lib/async/index.ts +11 -0
  460. package/src/shared/lib/async/run-all.ts +19 -0
  461. package/src/shared/lib/bytes/bytes.ts +53 -0
  462. package/src/shared/lib/bytes/index.ts +9 -0
  463. package/src/shared/lib/clock/clock.ts +114 -0
  464. package/src/shared/lib/clock/index.ts +2 -0
  465. package/src/shared/lib/cookies/index.ts +2 -0
  466. package/src/shared/lib/cookies/set-cookie.ts +105 -0
  467. package/src/shared/lib/envelope/envelope.ts +133 -0
  468. package/src/shared/lib/envelope/index.ts +9 -0
  469. package/src/shared/lib/errors/index.ts +5 -0
  470. package/src/shared/lib/errors/wirestage-error.ts +50 -0
  471. package/src/shared/lib/id/id.ts +29 -0
  472. package/src/shared/lib/id/index.ts +1 -0
  473. package/src/shared/lib/logger/index.ts +12 -0
  474. package/src/shared/lib/logger/logger.ts +126 -0
  475. package/src/shared/lib/match/index.ts +6 -0
  476. package/src/shared/lib/match/url-matcher.ts +107 -0
  477. package/src/sse.ts +2 -0
  478. package/src/webrtc.ts +2 -0
  479. package/src/websocket.ts +2 -0
  480. package/src/widgets/stage/index.ts +9 -0
  481. package/src/widgets/stage/lib/attached-context.ts +10 -0
  482. package/src/widgets/stage/lib/create-stage.ts +307 -0
  483. package/src/widgets/stage/lib/http-router.ts +954 -0
  484. package/src/widgets/stage/lib/page-host.ts +375 -0
  485. package/src/widgets/stage/lib/page-runtime.ts +314 -0
  486. package/src/widgets/stage/lib/reads.ts +128 -0
  487. package/src/widgets/stage/lib/response-stream.ts +139 -0
  488. package/src/widgets/stage/lib/socket-router.ts +300 -0
  489. package/src/widgets/stage/model/stage.ts +110 -0
@@ -0,0 +1,375 @@
1
+ import type {
2
+ PageEntry,
3
+ PageHost,
4
+ PageModule,
5
+ } from "../../../entities/plugin/index.js";
6
+ import type {
7
+ BindingSourcePort,
8
+ ContextPort,
9
+ FramePort,
10
+ PagePort,
11
+ } from "../../../shared/api/playwright/index.js";
12
+ import type { Clock } from "../../../shared/lib/clock/index.js";
13
+ import { WirestageError } from "../../../shared/lib/errors/index.js";
14
+ import type { ScopedLogger } from "../../../shared/lib/logger/index.js";
15
+ import type { AttachedContext } from "./attached-context.js";
16
+ import {
17
+ buildPageScript,
18
+ type CommandResult,
19
+ commandRuntime,
20
+ detachRuntime,
21
+ flushRuntime,
22
+ type RuntimeMessage,
23
+ } from "./page-runtime.js";
24
+
25
+ export interface PageHostDeps {
26
+ readonly stage: string;
27
+ readonly log: ScopedLogger;
28
+ readonly clock: Clock;
29
+ readonly pageId: (page: PagePort) => string;
30
+ /** How long entries may wait for company before a batch leaves. */
31
+ readonly flushMs: number;
32
+ /**
33
+ * How long a frame may take to answer a flush or a command, and Playwright
34
+ * to take the binding or the init script out at detach. Whatever takes
35
+ * longer is passed over, not waited for: a page stuck behind an open
36
+ * dialog must not stall every read of the timeline, nor the teardown.
37
+ */
38
+ readonly timeoutMs: number;
39
+ }
40
+
41
+ export interface PageHostEngine extends PageHost {
42
+ attach(attachment: AttachedContext): Promise<void>;
43
+ detach(attachment: AttachedContext): Promise<void>;
44
+ /** Flush every open frame's buffer into the modules, then return. */
45
+ sync(): Promise<void>;
46
+ }
47
+
48
+ /** What recent Playwright returns from `exposeBinding` / `addInitScript`. */
49
+ type MaybeDisposable = { dispose?: () => Promise<void> } | undefined;
50
+
51
+ /** What the host put into one browser context. */
52
+ interface Installed {
53
+ /** The attachment its entries belong to; none while detached. */
54
+ current: AttachedContext | undefined;
55
+ /**
56
+ * Take the binding and the init script back out. Unset until each is in,
57
+ * and where this Playwright returned nothing to take them out with.
58
+ */
59
+ removeBinding?: () => Promise<void>;
60
+ removeScript?: () => Promise<void>;
61
+ }
62
+
63
+ /** How to take a hook back out, where this Playwright can. */
64
+ const remover = (handle: MaybeDisposable) =>
65
+ handle?.dispose === undefined
66
+ ? undefined
67
+ : () => (handle.dispose as () => Promise<void>)();
68
+
69
+ /**
70
+ * Node half of the page host: one binding and one init script per context,
71
+ * carrying every registered module.
72
+ *
73
+ * Modules are sealed at the first attach. An init script cannot be amended
74
+ * after the fact, so a module registered later would silently be missing from
75
+ * pages of the contexts already attached; failing loudly is the better DX.
76
+ */
77
+ export function createPageHost(deps: PageHostDeps): PageHostEngine {
78
+ const modules = new Map<string, PageModule<unknown, unknown>>();
79
+ const attachments = new Set<AttachedContext>();
80
+ const installed = new WeakMap<ContextPort, Installed>();
81
+ const binding = `__wirestage_${deps.stage}`;
82
+ const { flushMs, timeoutMs } = deps;
83
+ let sealed = false;
84
+
85
+ /** Every frame of every open page: page hooks run in iframes too. */
86
+ const openFrames = (
87
+ among: Iterable<AttachedContext>,
88
+ pageFilter?: string,
89
+ ): FramePort[] => {
90
+ const frames: FramePort[] = [];
91
+ for (const attachment of among)
92
+ for (const page of attachment.context.pages())
93
+ if (
94
+ !page.isClosed() &&
95
+ (pageFilter === undefined || deps.pageId(page) === pageFilter)
96
+ )
97
+ frames.push(...page.frames());
98
+ return frames;
99
+ };
100
+
101
+ /**
102
+ * `work`'s value, or undefined once `timeoutMs` have passed — with `late`
103
+ * logged, and the work left to finish on its own.
104
+ */
105
+ const inTime = async <T>(
106
+ work: Promise<T>,
107
+ late: string,
108
+ ): Promise<{ readonly value: T } | undefined> => {
109
+ const waiting = new AbortController();
110
+ try {
111
+ return await Promise.race([
112
+ work.then((value) => ({ value })),
113
+ deps.clock.sleep(timeoutMs, waiting.signal).then(
114
+ () => {
115
+ deps.log.warn(late, { timeoutMs });
116
+ return undefined;
117
+ },
118
+ // The work finished first.
119
+ () => undefined,
120
+ ),
121
+ ]);
122
+ } finally {
123
+ waiting.abort();
124
+ }
125
+ };
126
+
127
+ /**
128
+ * Evaluate in one frame, within the time a frame is given. A frame that
129
+ * is navigating or gone has nothing to say, which is not a failure.
130
+ */
131
+ const ask = async <Arg, Result>(
132
+ frame: FramePort,
133
+ pageFunction: (arg: Arg) => Result | Promise<Result>,
134
+ arg: Arg,
135
+ what: string,
136
+ ): Promise<{ readonly value: Awaited<Result> } | undefined> => {
137
+ try {
138
+ return await inTime(
139
+ frame.evaluate(pageFunction, arg) as Promise<Awaited<Result>>,
140
+ `a frame did not answer a ${what} in time and was passed over; what it recorded may be missing (is a dialog open?)`,
141
+ );
142
+ } catch (error) {
143
+ deps.log.debug(`${what} skipped`, {}, error);
144
+ return undefined;
145
+ }
146
+ };
147
+
148
+ /**
149
+ * Take the binding or the init script back out, within the same time.
150
+ * Playwright's `dispose()` reaches every page of the context, and a page
151
+ * with a dialog open answers nothing until the dialog closes: a detach
152
+ * that waited for it would hang the test's teardown.
153
+ */
154
+ const release = async (
155
+ remove: () => Promise<void>,
156
+ what: string,
157
+ ): Promise<void> => {
158
+ try {
159
+ await inTime(
160
+ remove(),
161
+ `the page ${what} was not taken out in time; detaching goes on, and Playwright removes it once the page responds (is a dialog open?)`,
162
+ );
163
+ } catch (error) {
164
+ // A context that closed first took it with it.
165
+ deps.log.debug("page hook already gone", {}, error);
166
+ }
167
+ };
168
+
169
+ const receive = (
170
+ source: BindingSourcePort,
171
+ message: RuntimeMessage,
172
+ attachment: AttachedContext,
173
+ ): unknown => {
174
+ if (message.type === "hello") {
175
+ const states: Record<string, unknown> = {};
176
+ for (const [id, module] of modules)
177
+ try {
178
+ states[id] = module.state();
179
+ } catch (error) {
180
+ // One plugin's bug: its module starts without a state, every
181
+ // other module with its own, and the log says whose it was.
182
+ deps.log.error(
183
+ "page module failed to give its state",
184
+ { module: id },
185
+ error,
186
+ );
187
+ }
188
+ return states;
189
+ }
190
+ const page = deps.pageId(source.page);
191
+ if (message.type === "failure") {
192
+ deps.log.error(
193
+ `page module failed in its ${message.handler} handler`,
194
+ { module: message.module, page },
195
+ message.error,
196
+ );
197
+ return undefined;
198
+ }
199
+ const byModule = new Map<string, PageEntry[]>();
200
+ for (const { module, entry } of message.entries) {
201
+ const list = byModule.get(module) ?? [];
202
+ list.push(entry as PageEntry);
203
+ byModule.set(module, list);
204
+ }
205
+ for (const [id, entries] of byModule) {
206
+ const module = modules.get(id);
207
+ if (module === undefined) continue;
208
+ try {
209
+ module.receive(entries, { page, context: attachment.id });
210
+ } catch (error) {
211
+ deps.log.error(
212
+ "page module failed to receive entries",
213
+ { module: id },
214
+ error,
215
+ );
216
+ }
217
+ }
218
+ return undefined;
219
+ };
220
+
221
+ const command = async (
222
+ module: string,
223
+ op: string,
224
+ args?: unknown,
225
+ options: { readonly page?: string } = {},
226
+ ): Promise<unknown[]> => {
227
+ // No module, no runtime handler: a frame would have nothing to answer.
228
+ if (!modules.has(module)) return [];
229
+ const results = await Promise.all(
230
+ openFrames(attachments, options.page).map((frame) =>
231
+ ask(frame, commandRuntime, { binding, module, op, args }, "command"),
232
+ ),
233
+ );
234
+ const answers: unknown[] = [];
235
+ const failures: string[] = [];
236
+ for (const result of results) {
237
+ // No answer, or a frame the init script never ran in.
238
+ const outcome: CommandResult | undefined = result?.value;
239
+ if (outcome === undefined) continue;
240
+ if ("error" in outcome) failures.push(outcome.error);
241
+ else answers.push(outcome.value);
242
+ }
243
+ if (failures.length > 0)
244
+ // Not "no frame answered": the module's own code failed in the page.
245
+ throw new WirestageError(
246
+ "INVALID_STATE",
247
+ `page module "${module}" failed to run "${op}" in ${failures.length} frame(s): ${failures[0]}`,
248
+ { details: { module, op, failures } },
249
+ );
250
+ return answers;
251
+ };
252
+
253
+ return {
254
+ register(module) {
255
+ if (sealed)
256
+ throw new WirestageError(
257
+ "INVALID_STATE",
258
+ `page module "${module.id}" registered after the stage was attached; register every plugin before calling attach()`,
259
+ );
260
+ if (modules.has(module.id))
261
+ throw new WirestageError(
262
+ "INVALID_ARGUMENT",
263
+ `page module "${module.id}" is already registered`,
264
+ );
265
+ try {
266
+ // Parsed the way the init script will use it. A source that is
267
+ // not an expression — a method, a bound or a native function —
268
+ // is a SyntaxError there that takes every module down with it,
269
+ // and nothing in Node would say so.
270
+ new Function(`return (${module.install.toString()});`);
271
+ } catch (error) {
272
+ throw new WirestageError(
273
+ "INVALID_ARGUMENT",
274
+ `page module "${module.id}" cannot run in the page: its install does not serialize to a function expression (${String(error)}); declare it with \`function\` or as an arrow function, not as an object method or a bound function`,
275
+ { cause: error },
276
+ );
277
+ }
278
+ modules.set(module.id, module as PageModule<unknown, unknown>);
279
+ },
280
+ async attach(attachment) {
281
+ sealed = true;
282
+ attachments.add(attachment);
283
+ if (modules.size === 0) return;
284
+ const before = installed.get(attachment.context);
285
+ if (before !== undefined) {
286
+ // Still in the context from an earlier attach this Playwright
287
+ // could not take back: point it at the new attachment.
288
+ before.current = attachment;
289
+ return;
290
+ }
291
+ const slot: Installed = { current: attachment };
292
+ installed.set(attachment.context, slot);
293
+ const installers: Record<string, (env: unknown) => void> = {};
294
+ for (const [id, module] of modules)
295
+ installers[id] = module.install as (env: unknown) => void;
296
+ try {
297
+ slot.removeBinding = remover(
298
+ (await attachment.context.exposeBinding(binding, (source, message) =>
299
+ slot.current === undefined
300
+ ? undefined
301
+ : receive(source, message as RuntimeMessage, slot.current),
302
+ )) as MaybeDisposable,
303
+ );
304
+ slot.removeScript = remover(
305
+ (await attachment.context.addInitScript({
306
+ content: buildPageScript(
307
+ {
308
+ binding,
309
+ flushMs,
310
+ modules: [...modules].map(([id, m]) => ({
311
+ id,
312
+ config: m.config,
313
+ })),
314
+ },
315
+ installers,
316
+ ),
317
+ })) as MaybeDisposable,
318
+ );
319
+ } catch (error) {
320
+ installed.delete(attachment.context);
321
+ // A binding left behind would make the next attach fail with
322
+ // "already registered".
323
+ if (slot.removeBinding !== undefined)
324
+ await release(slot.removeBinding, "binding");
325
+ throw error;
326
+ }
327
+ },
328
+ async detach(attachment) {
329
+ if (!attachments.delete(attachment)) return;
330
+ const slot = installed.get(attachment.context);
331
+ if (slot === undefined) return;
332
+ const { removeBinding, removeScript } = slot;
333
+ // A Playwright that cannot take a binding or an init script back
334
+ // leaves them in the context, switched off; they serve it again if
335
+ // it is attached again.
336
+ const removable =
337
+ removeBinding !== undefined && removeScript !== undefined;
338
+ if (removable) {
339
+ installed.delete(attachment.context);
340
+ // The init script first: a document that loaded while the open
341
+ // ones were being detached would start a runtime no detach
342
+ // reaches, and then lose its binding under it.
343
+ await release(removeScript, "init script");
344
+ }
345
+ // Undo the hooks in the documents that are open, and take what they
346
+ // still hold, while the binding is there to carry it.
347
+ await Promise.all(
348
+ openFrames([attachment]).map((frame) =>
349
+ ask(frame, detachRuntime, binding, "detach"),
350
+ ),
351
+ );
352
+ slot.current = undefined;
353
+ if (removable) await release(removeBinding, "binding");
354
+ },
355
+ async sync() {
356
+ if (modules.size === 0) return;
357
+ await Promise.all(
358
+ openFrames(attachments).map((frame) =>
359
+ ask(frame, flushRuntime, binding, "flush"),
360
+ ),
361
+ );
362
+ },
363
+ command,
364
+ async refresh(module) {
365
+ const target = modules.get(module);
366
+ if (target === undefined)
367
+ throw new WirestageError(
368
+ "INVALID_ARGUMENT",
369
+ `no page module "${module}" is registered`,
370
+ );
371
+ await command(module, "__state", target.state());
372
+ },
373
+ pageId: deps.pageId,
374
+ };
375
+ }
@@ -0,0 +1,314 @@
1
+ /**
2
+ * The page half of the page host. Everything in this file that is exported as
3
+ * a function runs INSIDE the page: it is serialized with `toString()`, so it is
4
+ * self-contained — no imports, no references to module scope.
5
+ */
6
+
7
+ export interface PageRuntimeInit {
8
+ /** The binding Node exposed; also the runtime's key in `__wirestage`. */
9
+ readonly binding: string;
10
+ /** How long entries may wait for company before the batch is flushed. */
11
+ readonly flushMs: number;
12
+ readonly modules: readonly {
13
+ readonly id: string;
14
+ readonly config: unknown;
15
+ }[];
16
+ }
17
+
18
+ /** Messages the runtime sends through the binding. */
19
+ export type RuntimeMessage =
20
+ | { readonly type: "hello" }
21
+ | {
22
+ readonly type: "entries";
23
+ readonly entries: readonly {
24
+ readonly module: string;
25
+ readonly entry: unknown;
26
+ }[];
27
+ }
28
+ | {
29
+ /** A module's own handler threw, with no call to reject: Node logs it. */
30
+ readonly type: "failure";
31
+ readonly module: string;
32
+ /** `install`, `onState` or `onDetach`. */
33
+ readonly handler: string;
34
+ readonly error: string;
35
+ };
36
+
37
+ /** What the runtime publishes on `globalThis.__wirestage[binding]`. */
38
+ export interface RuntimeHandle {
39
+ flush(): Promise<unknown>;
40
+ command(module: string, op: string, args: unknown): unknown;
41
+ /** Undo every module's hooks, send what is still held, and step aside. */
42
+ detach(): Promise<unknown>;
43
+ }
44
+
45
+ /** A command's answer, or why its handler failed. */
46
+ export type CommandResult =
47
+ | { readonly value: unknown }
48
+ | { readonly error: string };
49
+
50
+ type Installer = (env: unknown) => void;
51
+
52
+ /**
53
+ * Install every module, batch what they emit, and answer Node's flushes and
54
+ * commands.
55
+ *
56
+ * Batching is what keeps a 60 Hz channel cheap: one binding call per flush
57
+ * window instead of one per message. The first entry after a quiet spell is
58
+ * not held back, though — it leaves at once, so it reaches Node ahead of a
59
+ * request the page makes right after it and the two land on the timeline in
60
+ * the order they happened.
61
+ *
62
+ * `flush()` returns the binding's promise, so a Node-side `evaluate(flush)`
63
+ * resolves only after Node has appended the entries: a read that flushes
64
+ * first sees everything the page recorded before it.
65
+ *
66
+ * Nothing the runtime does throws into the page's own code: a module whose
67
+ * handler throws is reported to Node, and a binding that has been taken away
68
+ * makes the runtime step aside.
69
+ */
70
+ export function pageRuntime(
71
+ scope: Record<string, unknown>,
72
+ init: PageRuntimeInit,
73
+ installers: Record<string, Installer>,
74
+ ): void {
75
+ scope["__wirestage"] ??= {};
76
+ const root = scope["__wirestage"] as Record<string, unknown>;
77
+ const bind = scope[init.binding];
78
+ // Init scripts run again in every frame and on every navigation; a second
79
+ // run in the same realm would double every entry.
80
+ if (root[init.binding] !== undefined || typeof bind !== "function") return;
81
+ const send = bind as (message: RuntimeMessage) => Promise<unknown>;
82
+
83
+ const queue: { module: string; entry: unknown }[] = [];
84
+ /** Set once the runtime steps aside: a module still emitting talks to nobody. */
85
+ let detached = false;
86
+ let timer: ReturnType<typeof setTimeout> | undefined;
87
+ /** When the last batch left; far enough back that the first entry is not held. */
88
+ let sentAt = Number.NEGATIVE_INFINITY;
89
+ const commands = new Map<string, (args: unknown) => unknown>();
90
+ const stateHandlers = new Map<string, ((state: unknown) => void)[]>();
91
+ const detachHandlers: { readonly module: string; undo(): void }[] = [];
92
+
93
+ const now = () => performance.timeOrigin + performance.now();
94
+ /** Undo every module's hooks, newest first: stacked hooks come off in order. */
95
+ const unhook = () => {
96
+ for (const { module, undo } of detachHandlers.splice(0).reverse()) {
97
+ try {
98
+ undo();
99
+ } catch (error) {
100
+ // One module failing to undo must not keep the rest hooked.
101
+ report(module, "onDetach", error);
102
+ }
103
+ }
104
+ delete root[init.binding];
105
+ };
106
+ /**
107
+ * Send through the binding. Playwright's throws, synchronously, once it
108
+ * has been removed — in a document the stage's detach did not reach, one
109
+ * that loaded while the stage was detaching. Thrown on from `emit`, that
110
+ * would fail the app's own call (`RTCDataChannel.send()`) or surface as a
111
+ * page error; nothing the runtime holds can reach Node any more, so it
112
+ * steps aside instead, putting back what the modules wrapped.
113
+ */
114
+ const post = (message: RuntimeMessage): Promise<unknown> => {
115
+ try {
116
+ return send(message);
117
+ } catch {
118
+ detached = true;
119
+ queue.length = 0;
120
+ unhook();
121
+ return Promise.resolve(undefined);
122
+ }
123
+ };
124
+ /** Tell Node a module's handler threw, so it is logged against the module. */
125
+ const report = (module: string, handler: string, error: unknown) => {
126
+ // Stepping aside for want of a binding: there is nobody left to tell.
127
+ if (detached) return;
128
+ post({ type: "failure", module, handler, error: String(error) }).catch(
129
+ () => {},
130
+ );
131
+ };
132
+ const flush = (): Promise<unknown> => {
133
+ if (timer !== undefined) {
134
+ clearTimeout(timer);
135
+ timer = undefined;
136
+ }
137
+ if (queue.length === 0) return Promise.resolve(undefined);
138
+ sentAt = now();
139
+ return post({ type: "entries", entries: queue.splice(0, queue.length) });
140
+ };
141
+ const schedule = () => {
142
+ const wait = sentAt + init.flushMs - now();
143
+ if (wait <= 0) {
144
+ flush().catch(() => {});
145
+ return;
146
+ }
147
+ timer ??= setTimeout(() => {
148
+ timer = undefined;
149
+ flush().catch(() => {});
150
+ }, wait);
151
+ };
152
+ const encode = (bytes: Uint8Array): string => {
153
+ let binary = "";
154
+ // Chunked: spreading a large array into fromCharCode overflows the stack.
155
+ for (let i = 0; i < bytes.length; i += 0x8000)
156
+ binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
157
+ return btoa(binary);
158
+ };
159
+ const decode = (text: string): Uint8Array => {
160
+ const binary = atob(text);
161
+ const out = new Uint8Array(binary.length);
162
+ for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
163
+ return out;
164
+ };
165
+ /** Run each of a module's state handlers, whatever the others do; return what threw. */
166
+ const applyState = (module: string, state: unknown): unknown[] => {
167
+ const failures: unknown[] = [];
168
+ for (const handler of stateHandlers.get(module) ?? [])
169
+ try {
170
+ handler(state);
171
+ } catch (error) {
172
+ failures.push(error);
173
+ }
174
+ return failures;
175
+ };
176
+
177
+ for (const module of init.modules) {
178
+ const install = installers[module.id];
179
+ if (install === undefined) continue;
180
+ const emit = (entry: unknown) => {
181
+ if (detached) return;
182
+ queue.push({ module: module.id, entry });
183
+ schedule();
184
+ };
185
+ try {
186
+ install({
187
+ window: scope,
188
+ config: module.config,
189
+ emit,
190
+ now,
191
+ encode,
192
+ decode,
193
+ onCommand: (op: string, handler: (args: unknown) => unknown) => {
194
+ commands.set(`${module.id}\u0000${op}`, handler);
195
+ },
196
+ onState: (handler: (state: unknown) => void) => {
197
+ const list = stateHandlers.get(module.id) ?? [];
198
+ list.push(handler);
199
+ stateHandlers.set(module.id, list);
200
+ },
201
+ onDetach: (handler: () => void) => {
202
+ detachHandlers.push({ module: module.id, undo: handler });
203
+ },
204
+ });
205
+ } catch (error) {
206
+ // A broken module must not take the page down with it: an entry puts
207
+ // the failure on its timeline, and Node logs it against the module.
208
+ emit({ type: "error", at: now(), text: String(error) });
209
+ report(module.id, "install", error);
210
+ }
211
+ }
212
+
213
+ const handle: RuntimeHandle = {
214
+ flush,
215
+ command(module, op, args) {
216
+ if (op === "__state") {
217
+ // Every handler gets the state; the command still fails when one
218
+ // threw, so the `refresh()` that sent it says which module broke.
219
+ const failures = applyState(module, args);
220
+ if (failures.length > 0) throw failures[0];
221
+ return true;
222
+ }
223
+ const handler = commands.get(`${module}\u0000${op}`);
224
+ return handler === undefined ? undefined : handler(args);
225
+ },
226
+ detach() {
227
+ unhook();
228
+ // After the undos: what they emit still leaves with the last flush.
229
+ detached = true;
230
+ return flush();
231
+ },
232
+ };
233
+ root[init.binding] = handle;
234
+ // Chromium loses a binding call made from `pagehide` when the main frame
235
+ // navigates — the document is gone before Node hears of it — while one
236
+ // made from `beforeunload` still arrives. `pagehide` stays for the ways
237
+ // out that fire no `beforeunload`: a page closed, an iframe removed.
238
+ for (const type of ["beforeunload", "pagehide"])
239
+ (scope as unknown as EventTarget).addEventListener(type, () => {
240
+ flush().catch(() => {});
241
+ });
242
+ post({ type: "hello" }).then(
243
+ (states) => {
244
+ const byModule = (states ?? {}) as Record<string, unknown>;
245
+ for (const module of stateHandlers.keys())
246
+ if (byModule[module] !== undefined)
247
+ // No call to fail: Node hears of each failure, and the
248
+ // modules after this one still get their state.
249
+ for (const error of applyState(module, byModule[module]))
250
+ report(module, "onState", error);
251
+ },
252
+ () => {},
253
+ );
254
+ }
255
+
256
+ /** Flush one frame's runtime. Runs in the page via `frame.evaluate`. */
257
+ export function flushRuntime(binding: string): Promise<unknown> | undefined {
258
+ const root = (globalThis as Record<string, unknown>)["__wirestage"] as
259
+ | Record<string, RuntimeHandle | undefined>
260
+ | undefined;
261
+ return root?.[binding]?.flush();
262
+ }
263
+
264
+ /** Detach one frame's runtime. Runs in the page via `frame.evaluate`. */
265
+ export function detachRuntime(binding: string): Promise<unknown> | undefined {
266
+ const root = (globalThis as Record<string, unknown>)["__wirestage"] as
267
+ | Record<string, RuntimeHandle | undefined>
268
+ | undefined;
269
+ return root?.[binding]?.detach();
270
+ }
271
+
272
+ /**
273
+ * Run one command in one frame's runtime. Runs in the page via
274
+ * `frame.evaluate`. Undefined when the frame has no runtime (a document the
275
+ * init script never ran in); otherwise the handler's answer, or its failure
276
+ * as text — an exception would cross back to Node as a rejected evaluate,
277
+ * indistinguishable from a frame that went away.
278
+ */
279
+ export function commandRuntime(arg: {
280
+ readonly binding: string;
281
+ readonly module: string;
282
+ readonly op: string;
283
+ readonly args: unknown;
284
+ }): Promise<CommandResult> | undefined {
285
+ const root = (globalThis as Record<string, unknown>)["__wirestage"] as
286
+ | Record<string, RuntimeHandle | undefined>
287
+ | undefined;
288
+ const handle = root?.[arg.binding];
289
+ if (handle === undefined) return undefined;
290
+ return Promise.resolve()
291
+ .then(() => handle.command(arg.module, arg.op, arg.args))
292
+ .then(
293
+ (value): CommandResult => ({ value }),
294
+ (error: unknown): CommandResult => ({ error: String(error) }),
295
+ );
296
+ }
297
+
298
+ /**
299
+ * The init script: the runtime, then every installer, then the call.
300
+ *
301
+ * Wrapped in a scope that defines `__name`: esbuild's `keepNames` (on by
302
+ * default in tsx) rewrites `const f = () => {}` into `__name(() => {}, "f")`,
303
+ * so code serialized from a tsx-run process calls a helper the page does not
304
+ * have, and the whole runtime dies with "__name is not defined".
305
+ */
306
+ export function buildPageScript(
307
+ init: PageRuntimeInit,
308
+ installers: Readonly<Record<string, Installer>>,
309
+ ): string {
310
+ const list = Object.entries(installers)
311
+ .map(([id, install]) => `${JSON.stringify(id)}: (${install.toString()})`)
312
+ .join(",\n");
313
+ return `(() => {\nconst __name = (target) => target;\n(${pageRuntime.toString()})(globalThis, ${JSON.stringify(init)}, {\n${list}\n});\n})();`;
314
+ }