@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,128 @@
1
+ import type {
2
+ Frame,
3
+ FrameFilter,
4
+ Timeline,
5
+ } from "../../../entities/frame/index.js";
6
+ import type { WaitForOptions } from "../../../entities/plugin/index.js";
7
+ import type { Clock } from "../../../shared/lib/clock/index.js";
8
+ import { WirestageError } from "../../../shared/lib/errors/index.js";
9
+ import type { StageCore } from "../model/stage.js";
10
+
11
+ export interface ReadsDeps {
12
+ readonly timeline: Timeline;
13
+ readonly clock: Clock;
14
+ /** Flush every page buffer into the timeline. */
15
+ readonly sync: () => Promise<void>;
16
+ /** Defaults for `waitFor`, in milliseconds. */
17
+ readonly wait: { readonly timeout: number; readonly interval: number };
18
+ }
19
+
20
+ /**
21
+ * Frames a report holds unless asked otherwise. A report is read from its
22
+ * end, and an unbounded one grows with the test: a long run would attach
23
+ * tens of megabytes, or fail to serialize at all.
24
+ */
25
+ const REPORT_LIMIT = 1_000;
26
+
27
+ export type StageReads = Pick<
28
+ StageCore,
29
+ "frames" | "drain" | "mark" | "since" | "between" | "waitFor" | "report"
30
+ >;
31
+
32
+ /**
33
+ * The stage's reads of its timeline. Every one flushes the page buffers
34
+ * first: that flush is what makes arrival order the order things happened in.
35
+ */
36
+ export function createReads(deps: ReadsDeps): StageReads {
37
+ const { timeline, clock, sync } = deps;
38
+ const drainCursor = timeline.cursor();
39
+
40
+ const requireMarker = (name: string): Frame => {
41
+ const marker = timeline.marker(name);
42
+ if (marker === undefined)
43
+ throw new WirestageError(
44
+ "INVALID_ARGUMENT",
45
+ timeline.evicted > 0
46
+ ? `no marker "${name}" on the timeline: it was never placed, or it was among the ${timeline.evicted} frame(s) dropped at capacity`
47
+ : `no marker "${name}" on the timeline; place one with stage.mark("${name}")`,
48
+ );
49
+ return marker;
50
+ };
51
+
52
+ return {
53
+ async frames(filter) {
54
+ await sync();
55
+ return timeline.frames(filter);
56
+ },
57
+ async drain(filter) {
58
+ await sync();
59
+ return drainCursor.take(filter);
60
+ },
61
+ async mark(name, meta) {
62
+ await sync();
63
+ return timeline.append({
64
+ protocol: "wirestage",
65
+ kind: "marker",
66
+ direction: "none",
67
+ endpoint: name,
68
+ source: "test",
69
+ ...(meta === undefined ? {} : { meta }),
70
+ });
71
+ },
72
+ async since(marker, filter) {
73
+ await sync();
74
+ const after = requireMarker(marker).seq;
75
+ return timeline.frames({
76
+ ...filter,
77
+ after: Math.max(after, filter?.after ?? 0),
78
+ });
79
+ },
80
+ async between(from, to, filter) {
81
+ await sync();
82
+ const start = requireMarker(from).seq;
83
+ const end = requireMarker(to).seq;
84
+ const where = filter?.where;
85
+ return timeline.frames({
86
+ ...filter,
87
+ after: Math.max(start, filter?.after ?? 0),
88
+ where: (frame) =>
89
+ frame.seq < end && (where === undefined || where(frame)),
90
+ });
91
+ },
92
+ async waitFor(filter: FrameFilter, options: WaitForOptions = {}) {
93
+ const count = options.count ?? 1;
94
+ const timeout = options.timeout ?? deps.wait.timeout;
95
+ const interval = options.interval ?? deps.wait.interval;
96
+ const deadline = clock.now() + timeout;
97
+ for (;;) {
98
+ await sync();
99
+ const found = timeline.frames(filter);
100
+ if (found.length >= count) return found;
101
+ const remaining = deadline - clock.now();
102
+ if (remaining <= 0)
103
+ throw new WirestageError(
104
+ "TIMEOUT",
105
+ `waited ${timeout} ms for ${count} frame(s) matching ${describeFilter(filter)}; ${found.length} matched. Last frames:\n${timeline.format(undefined, { limit: 20 })}`,
106
+ { details: { count, matched: found.length, timeout } },
107
+ );
108
+ await clock.sleep(Math.min(interval, remaining));
109
+ }
110
+ },
111
+ report(filter, options = {}) {
112
+ const limit = options.limit ?? REPORT_LIMIT;
113
+ return {
114
+ snapshot: timeline.snapshot(filter, { limit }),
115
+ text: timeline.format(filter, { limit }),
116
+ };
117
+ },
118
+ };
119
+ }
120
+
121
+ /** A filter as JSON, with RegExps and callbacks spelled out instead of lost. */
122
+ function describeFilter(filter: FrameFilter): string {
123
+ return JSON.stringify(filter, (_key, value: unknown) => {
124
+ if (value instanceof RegExp) return value.toString();
125
+ if (typeof value === "function") return "[function]";
126
+ return value;
127
+ });
128
+ }
@@ -0,0 +1,139 @@
1
+ import type {
2
+ IncomingMessage,
3
+ OutgoingHttpHeader,
4
+ ServerResponse,
5
+ } from "node:http";
6
+
7
+ import type {
8
+ ResponseStream,
9
+ StreamEnd,
10
+ } from "../../../entities/plugin/index.js";
11
+ import { createDeferred } from "../../../shared/lib/async/index.js";
12
+ import { toBytes } from "../../../shared/lib/bytes/index.js";
13
+ import { WirestageError } from "../../../shared/lib/errors/index.js";
14
+
15
+ /** A stream as the stage serves it: the rule's end of it, and the stage's. */
16
+ export interface ServedStream {
17
+ /** What the rule writes. */
18
+ readonly stream: ResponseStream;
19
+ /** The stub asked for the end, and `closed` is on its way. */
20
+ readonly closing: boolean;
21
+ /**
22
+ * The rule writing the stream failed: answer 500 with `body` while
23
+ * nothing has gone out, and cut the connection after. False when the
24
+ * response was over first — ended, cut, or let go of by the page.
25
+ */
26
+ fail(body: string): boolean;
27
+ }
28
+
29
+ /**
30
+ * A Node response as the stream a rule writes: the adapter between the
31
+ * stream server's sockets and the plugin contract, which knows nothing of
32
+ * Node. `onClose` hears how the response ended before `closed` resolves, so
33
+ * whoever awaits `closed` finds the end already on record.
34
+ */
35
+ export function createResponseStream(
36
+ request: IncomingMessage,
37
+ response: ServerResponse,
38
+ onClose: (end: StreamEnd, failed: boolean) => void,
39
+ ): ServedStream {
40
+ let state: "open" | "ended" | "cut" = "open";
41
+ let failed = false;
42
+ const closed = createDeferred<StreamEnd>();
43
+ response.once("close", () => {
44
+ // Not ended and not cut: the page let go of it.
45
+ const end = state === "open" ? "cancelled" : state;
46
+ onClose(end, failed);
47
+ closed.resolve(end);
48
+ });
49
+ /** The stream server's own headers (CORS, `connection`), before the rule's. */
50
+ const own = Object.entries(response.getHeaders());
51
+ /** Back to those: what a rule's head set is gone, what it replaced is back. */
52
+ const restore = () => {
53
+ for (const name of response.getHeaderNames()) response.removeHeader(name);
54
+ for (const [name, value] of own)
55
+ response.setHeader(name, value as OutgoingHttpHeader);
56
+ };
57
+ /**
58
+ * Whether a write can reach the page. Node drops a write — callback and
59
+ * all — into a socket destroyed or closed for writing, which is what the
60
+ * page letting go leaves, well before the response reports its close.
61
+ */
62
+ const writable = () => state === "open" && response.socket?.writable === true;
63
+ const requestHeaders: Record<string, string> = {};
64
+ for (const [name, value] of Object.entries(request.headers))
65
+ requestHeaders[name] = String(value);
66
+ return {
67
+ stream: {
68
+ requestHeaders,
69
+ head(status, headers = {}) {
70
+ // Node checks a status only as the head goes out, and refused by
71
+ // `end()` it leaves the response broken: a 500 after it has no body.
72
+ if (!Number.isInteger(status) || status < 100 || status > 999)
73
+ throw new WirestageError(
74
+ "INVALID_ARGUMENT",
75
+ `a response status is an integer from 100 to 999, got ${status}`,
76
+ );
77
+ // Kept off the wire until the first write or flush, so a rule that
78
+ // fails before then can still be answered with its 500.
79
+ restore();
80
+ response.statusCode = status;
81
+ for (const [name, value] of Object.entries(headers)) {
82
+ // The stream server's to say: one response per connection.
83
+ if (name.toLowerCase() === "connection") continue;
84
+ // Several values, one per line (`set-cookie`): Node sends a
85
+ // header line for each, and refuses a value with a newline.
86
+ const values = value.split("\n");
87
+ response.setHeader(name, values.length > 1 ? values : value);
88
+ }
89
+ },
90
+ flush() {
91
+ response.flushHeaders();
92
+ },
93
+ write(chunk) {
94
+ return new Promise<void>((resolve, reject) => {
95
+ if (!writable()) {
96
+ reject(
97
+ new WirestageError("CLOSED", "the response stream is closed"),
98
+ );
99
+ return;
100
+ }
101
+ response.write(toBytes(chunk), (error) =>
102
+ error ? reject(error) : resolve(),
103
+ );
104
+ });
105
+ },
106
+ async end() {
107
+ if (writable()) {
108
+ state = "ended";
109
+ response.end();
110
+ }
111
+ await closed.promise;
112
+ },
113
+ cut() {
114
+ if (!writable()) return;
115
+ state = "cut";
116
+ response.destroy();
117
+ },
118
+ closed: closed.promise,
119
+ },
120
+ get closing() {
121
+ return state !== "open" && !closed.settled;
122
+ },
123
+ fail(body) {
124
+ if (!writable()) return false;
125
+ failed = true;
126
+ if (response.headersSent) {
127
+ state = "cut";
128
+ response.destroy();
129
+ return true;
130
+ }
131
+ state = "ended";
132
+ restore();
133
+ response
134
+ .writeHead(500, { "content-type": "text/plain; charset=utf-8" })
135
+ .end(body);
136
+ return true;
137
+ },
138
+ };
139
+ }
@@ -0,0 +1,300 @@
1
+ import type { Timeline } from "../../../entities/frame/index.js";
2
+ import type {
3
+ SocketPeer,
4
+ SocketRouter,
5
+ SocketRule,
6
+ SocketRuleHandle,
7
+ SocketView,
8
+ } from "../../../entities/plugin/index.js";
9
+ import type { WebSocketRoutePort } from "../../../shared/api/playwright/index.js";
10
+ import { toBuffer } from "../../../shared/lib/bytes/index.js";
11
+ import { WirestageError } from "../../../shared/lib/errors/index.js";
12
+ import type { ScopedLogger } from "../../../shared/lib/logger/index.js";
13
+ import { compileUrlMatcher } from "../../../shared/lib/match/index.js";
14
+ import type { AttachedContext } from "./attached-context.js";
15
+
16
+ export interface SocketRouterDeps {
17
+ readonly timeline: Timeline;
18
+ readonly log: ScopedLogger;
19
+ readonly correlation: () => string;
20
+ }
21
+
22
+ export interface SocketRouterEngine extends SocketRouter {
23
+ attach(attachment: AttachedContext): Promise<void>;
24
+ detach(attachment: AttachedContext): void;
25
+ }
26
+
27
+ interface Entry {
28
+ readonly rule: SocketRule;
29
+ readonly matchesUrl: (url: URL) => boolean;
30
+ remaining: number | undefined;
31
+ hits: number;
32
+ active: boolean;
33
+ }
34
+
35
+ type CloseOptions = Parameters<SocketPeer["close"]>[0];
36
+
37
+ /** What a script may pass to `WebSocket.close()`: 1000, or 3000-4999. */
38
+ const sendable = (code: number): boolean =>
39
+ code === 1000 || (code >= 3000 && code <= 4999);
40
+
41
+ const encoder = new TextEncoder();
42
+
43
+ /**
44
+ * A close reason cut to what a close frame carries: 123 bytes of UTF-8 (the
45
+ * frame's 125, less the code), ending on a whole character.
46
+ */
47
+ const fitReason = (reason: string): string =>
48
+ // `encodeInto` stops before the first character that does not fit whole.
49
+ reason.slice(0, encoder.encodeInto(reason, new Uint8Array(123)).read);
50
+
51
+ /**
52
+ * A close as the server's end can carry it out. Playwright closes that end
53
+ * with the browser's own `WebSocket.close(code, reason)`, which throws on a
54
+ * code a script may not send, on a reason without a code and on a reason
55
+ * over 123 bytes — and Playwright swallows the error, which leaves the
56
+ * connection open for good. Such a close goes out as a normal closure, 1000,
57
+ * with its reason cut to fit: the close is the point.
58
+ */
59
+ const forServer = (options: CloseOptions): CloseOptions => {
60
+ const { code, reason } = options ?? {};
61
+ if (code === undefined && reason === undefined) return options;
62
+ return {
63
+ code: code !== undefined && sendable(code) ? code : 1000,
64
+ ...(reason === undefined ? {} : { reason: fitReason(reason) }),
65
+ };
66
+ };
67
+
68
+ /** The message handler of an end whose messages the rule does not take. */
69
+ const drop = (): void => {};
70
+
71
+ /**
72
+ * Turns WebSocket rules into ONE Playwright WebSocket route per attached
73
+ * context, whose predicate asks the live rule list.
74
+ *
75
+ * One route, because Playwright cannot take a WebSocket route out again: a
76
+ * removed rule, or a detached context, simply stops matching, which sends the
77
+ * socket to the real server. And not before it is needed, because routing
78
+ * replaces `WebSocket` in every document of the context — the first rule
79
+ * installs it, or `prepare()` does at attach.
80
+ */
81
+ export function createSocketRouter(deps: SocketRouterDeps): SocketRouterEngine {
82
+ /** Active rules, oldest first. */
83
+ const entries: Entry[] = [];
84
+ const attachments = new Set<AttachedContext>();
85
+ const routes = new Map<AttachedContext, Promise<void>>();
86
+ let prepared = false;
87
+
88
+ /** The newest rule that wants a socket to `url`. */
89
+ const taker = (url: URL): Entry | undefined =>
90
+ entries.findLast((entry) => {
91
+ try {
92
+ return entry.matchesUrl(url);
93
+ } catch (error) {
94
+ // The caller's predicate; one that throws must not break every
95
+ // socket of the context.
96
+ deps.log.error(
97
+ "rule's URL matcher failed",
98
+ { rule: entry.rule.label, url: url.href },
99
+ error,
100
+ );
101
+ return false;
102
+ }
103
+ });
104
+
105
+ const retire = (entry: Entry) => {
106
+ if (!entry.active) return;
107
+ entry.active = false;
108
+ entries.splice(entries.indexOf(entry), 1);
109
+ };
110
+
111
+ /**
112
+ * One end, as a rule drives it. Playwright calls the rule's handlers from
113
+ * its own event dispatch, where a throw is an uncaught exception and a
114
+ * rejection an unhandled one — either surfacing in whichever test runs
115
+ * next. A handler that fails fails the rule instead, as `open` does.
116
+ */
117
+ const peer = (
118
+ route: WebSocketRoutePort,
119
+ onFailure: (error: unknown) => Promise<void>,
120
+ close: (options: CloseOptions) => CloseOptions = (options) => options,
121
+ ): SocketPeer => {
122
+ const guarded =
123
+ <Args extends unknown[]>(handler: (...args: Args) => void) =>
124
+ (...args: Args): void => {
125
+ try {
126
+ // Typed `void`, yet an async function is a valid handler.
127
+ const result: unknown = handler(...args);
128
+ if (result instanceof Promise) result.catch(onFailure);
129
+ } catch (error) {
130
+ void onFailure(error);
131
+ }
132
+ };
133
+ return {
134
+ send: (data) =>
135
+ route.send(typeof data === "string" ? data : toBuffer(data)),
136
+ close: (options) => route.close(close(options)),
137
+ onMessage: (handler) => route.onMessage(guarded(handler)),
138
+ onClose: (handler) => route.onClose(guarded(handler)),
139
+ };
140
+ };
141
+
142
+ /**
143
+ * A rule that throws, in `open` or in a handler it set, is a bug in the
144
+ * test. Say which rule failed — in the log, on the timeline, and in the
145
+ * reason the page's socket closes with — and close both ends: the page's
146
+ * socket would otherwise sit open with nobody answering it, and a server
147
+ * the rule connected would stay connected. 1011 is a server's "internal
148
+ * error"; the server's end gets 1000, as a script may not send 1011.
149
+ */
150
+ const fail = async (
151
+ rule: SocketRule,
152
+ view: SocketView,
153
+ server: SocketPeer | undefined,
154
+ error: unknown,
155
+ ): Promise<void> => {
156
+ const message = error instanceof Error ? error.message : String(error);
157
+ deps.log.error(
158
+ "socket rule failed",
159
+ { rule: rule.label, url: view.url.href },
160
+ error,
161
+ );
162
+ deps.timeline.append({
163
+ protocol: rule.protocol,
164
+ kind: "fault",
165
+ direction: "none",
166
+ endpoint: view.url.pathname,
167
+ source: "stub",
168
+ correlation: view.correlation,
169
+ context: view.context,
170
+ meta: { reason: "rule-error", rule: rule.label, error: message },
171
+ });
172
+ const closing = {
173
+ code: 1011,
174
+ // Cut like the server's end, though the page's would take it whole:
175
+ // no real server could send the page a longer one.
176
+ reason: fitReason(`wirestage: rule "${rule.label}" failed: ${message}`),
177
+ };
178
+ await Promise.all([view.page.close(closing), server?.close(closing)]);
179
+ };
180
+
181
+ const onSocket = async (
182
+ attachment: AttachedContext,
183
+ route: WebSocketRoutePort,
184
+ ): Promise<void> => {
185
+ const url = new URL(route.url());
186
+ const entry = attachments.has(attachment) ? taker(url) : undefined;
187
+ // The rule went, or the context was detached, between Playwright's
188
+ // check and this call: the socket is the real server's.
189
+ if (entry === undefined) {
190
+ route.connectToServer();
191
+ return;
192
+ }
193
+ entry.hits += 1;
194
+ if (entry.remaining !== undefined && --entry.remaining === 0) retire(entry);
195
+ // Playwright passes the messages of an end with no handler on to the
196
+ // other end, unrecorded: a rule that wires one direction would leak
197
+ // the other. So each end starts with a handler that drops them, which
198
+ // the rule's own replaces.
199
+ route.onMessage(drop);
200
+ let server: SocketPeer | undefined;
201
+ let failed = false;
202
+ // Once per socket, for whatever fails first. Marked before failing:
203
+ // the closes that follow are reported to the rule's handlers, and a
204
+ // broken one throws again.
205
+ const failOnce = async (error: unknown): Promise<void> => {
206
+ if (failed) return;
207
+ failed = true;
208
+ await fail(entry.rule, view, server, error);
209
+ };
210
+ const view: SocketView = {
211
+ correlation: deps.correlation(),
212
+ url,
213
+ context: attachment.id,
214
+ page: peer(route, failOnce),
215
+ connectToServer: () => {
216
+ if (server === undefined) {
217
+ const upstream = route.connectToServer();
218
+ upstream.onMessage(drop);
219
+ server = peer(upstream, failOnce, forServer);
220
+ }
221
+ return server;
222
+ },
223
+ };
224
+ try {
225
+ // Awaited: Playwright opens the page's end of a socket the rule
226
+ // did not connect once this handler settles, so an async rule is
227
+ // wired up before the first message.
228
+ await entry.rule.open(view);
229
+ } catch (error) {
230
+ // Never rejected to Playwright, where it would surface as an
231
+ // unhandled rejection in whichever test runs next.
232
+ await failOnce(error);
233
+ }
234
+ };
235
+
236
+ const ensureRoute = (attachment: AttachedContext): Promise<void> => {
237
+ let installing = routes.get(attachment);
238
+ if (installing === undefined) {
239
+ installing = attachment.context.routeWebSocket(
240
+ (url) => attachments.has(attachment) && taker(url) !== undefined,
241
+ (route) => onSocket(attachment, route),
242
+ );
243
+ routes.set(attachment, installing);
244
+ // A failed install must not be remembered as done.
245
+ installing.catch(() => routes.delete(attachment));
246
+ }
247
+ return installing;
248
+ };
249
+
250
+ return {
251
+ async add(rule): Promise<SocketRuleHandle> {
252
+ if (
253
+ rule.times !== undefined &&
254
+ (!Number.isInteger(rule.times) || rule.times < 1)
255
+ )
256
+ throw new WirestageError(
257
+ "INVALID_ARGUMENT",
258
+ `rule "${rule.label}": times must be a positive integer, got ${rule.times}`,
259
+ );
260
+ const entry: Entry = {
261
+ rule,
262
+ matchesUrl: compileUrlMatcher(rule.url),
263
+ remaining: rule.times,
264
+ hits: 0,
265
+ active: true,
266
+ };
267
+ entries.push(entry);
268
+ try {
269
+ await Promise.all([...attachments].map(ensureRoute));
270
+ } catch (error) {
271
+ retire(entry);
272
+ throw error;
273
+ }
274
+ return {
275
+ get hits() {
276
+ return entry.hits;
277
+ },
278
+ get active() {
279
+ return entry.active;
280
+ },
281
+ async remove() {
282
+ retire(entry);
283
+ },
284
+ };
285
+ },
286
+ prepare() {
287
+ prepared = true;
288
+ },
289
+ async attach(attachment) {
290
+ attachments.add(attachment);
291
+ if (prepared || entries.length > 0) await ensureRoute(attachment);
292
+ },
293
+ detach(attachment) {
294
+ attachments.delete(attachment);
295
+ // The route stays in the browser; a context attached again gets a
296
+ // new one, and this one's predicate never matches again.
297
+ routes.delete(attachment);
298
+ },
299
+ };
300
+ }
@@ -0,0 +1,110 @@
1
+ import type {
2
+ Frame,
3
+ FrameFilter,
4
+ Timeline,
5
+ TimelineSnapshot,
6
+ } from "../../../entities/frame/index.js";
7
+ import type {
8
+ AnyWirePlugin,
9
+ Attachment,
10
+ PluginApis,
11
+ WaitForOptions,
12
+ } from "../../../entities/plugin/index.js";
13
+ import type {
14
+ ContextPort,
15
+ PagePort,
16
+ } from "../../../shared/api/playwright/index.js";
17
+ import type { StreamServerOptions } from "../../../shared/api/stream-server/index.js";
18
+ import type { Clock } from "../../../shared/lib/clock/index.js";
19
+
20
+ export interface StageOptions<Plugins extends readonly AnyWirePlugin[]> {
21
+ /** The protocols this stage handles. Their APIs appear as `stage.<name>`. */
22
+ readonly plugins: Plugins;
23
+ /** Used by drive clients that are not given a URL of their own. */
24
+ readonly baseURL?: string;
25
+ /** Frames the timeline keeps. Default 100,000. */
26
+ readonly capacity?: number;
27
+ /**
28
+ * How long a page batches what it records before sending it on. Default
29
+ * 25 ms. The first entry after a quiet spell is sent at once.
30
+ */
31
+ readonly pageFlushMs?: number;
32
+ /**
33
+ * How long a page may take to answer a flush or a command, or the
34
+ * browser to hand over a response's body, before a read goes on without
35
+ * it; how long a live stream's request may take to reach the stream
36
+ * server before the stream is given up; and how long Playwright may take
37
+ * to take the page hooks out before a detach goes on. Default 2000 ms.
38
+ */
39
+ readonly pageTimeoutMs?: number;
40
+ /** TLS for live streams on https pages, and the bind host. */
41
+ readonly streamServer?: Pick<StreamServerOptions, "tls" | "host">;
42
+ /** Defaults for `waitFor`. */
43
+ readonly wait?: Pick<WaitForOptions, "timeout" | "interval">;
44
+ /** Replace time, for tests of the stage itself. */
45
+ readonly clock?: Clock;
46
+ }
47
+
48
+ export interface AttachOptions {
49
+ /** The context's base URL, for drive clients bound to it. */
50
+ readonly baseURL?: string;
51
+ }
52
+
53
+ export interface StageReport {
54
+ readonly snapshot: TimelineSnapshot;
55
+ readonly text: string;
56
+ }
57
+
58
+ export interface ReportOptions {
59
+ /** Frames kept, counting back from the newest. Default 1,000. */
60
+ readonly limit?: number;
61
+ }
62
+
63
+ /** The stage itself, before plugin APIs are mounted on it. */
64
+ export interface StageCore {
65
+ readonly id: string;
66
+ /** The raw timeline. Reads here do NOT flush page buffers first; prefer the methods below. */
67
+ readonly timeline: Timeline;
68
+ readonly attachments: readonly Attachment[];
69
+ readonly disposed: boolean;
70
+ /**
71
+ * Start handling a browser context: install every plugin's routes and page
72
+ * hooks. Call before the first navigation. Returns a function that
73
+ * detaches. A failed attach is undone, so it can be tried again.
74
+ */
75
+ attach(
76
+ context: ContextPort,
77
+ options?: AttachOptions,
78
+ ): Promise<() => Promise<void>>;
79
+ /** Detach one context, or all of them. */
80
+ detach(context?: ContextPort): Promise<void>;
81
+ /** The id frames use for this page. */
82
+ pageId(page: PagePort): string;
83
+ /**
84
+ * Bring the timeline up to date: flush every page's buffer, and wait for
85
+ * the responses already back from the network and the ends of the
86
+ * streams a stub closed. The reads below do it for you.
87
+ */
88
+ sync(): Promise<void>;
89
+ frames(filter?: FrameFilter): Promise<Frame[]>;
90
+ /** Frames since the previous `drain` (any filter), narrowed by `filter`. */
91
+ drain(filter?: FrameFilter): Promise<Frame[]>;
92
+ /** Put a named marker on the timeline, after everything that happened so far. */
93
+ mark(name: string, meta?: Readonly<Record<string, unknown>>): Promise<Frame>;
94
+ /** Frames after the most recent marker called `marker`. */
95
+ since(marker: string, filter?: FrameFilter): Promise<Frame[]>;
96
+ /** Frames between two markers, exclusive. */
97
+ between(from: string, to: string, filter?: FrameFilter): Promise<Frame[]>;
98
+ /** Resolve with the matching frames once `count` of them exist. */
99
+ waitFor(filter: FrameFilter, options?: WaitForOptions): Promise<Frame[]>;
100
+ /** The newest frames as JSON and as text. Does not flush: `sync()` first. */
101
+ report(filter?: FrameFilter, options?: ReportOptions): StageReport;
102
+ /**
103
+ * Detach everything, stop servers, release plugins. Idempotent. Every
104
+ * step runs even when one fails; what failed is thrown at the end.
105
+ */
106
+ dispose(): Promise<void>;
107
+ }
108
+
109
+ export type Stage<Plugins extends readonly AnyWirePlugin[] = readonly []> =
110
+ StageCore & PluginApis<Plugins>;