@apifuse/provider-sdk 2.2.0-beta.7 → 2.2.0-beta.70

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 (478) hide show
  1. package/AUTHORING.md +943 -51
  2. package/CHANGELOG.md +273 -1
  3. package/LICENSE +21 -0
  4. package/README.md +117 -29
  5. package/SUBMISSION.md +3 -2
  6. package/bin/apifuse-check.ts +293 -13
  7. package/bin/apifuse-dev.ts +42 -19
  8. package/bin/apifuse-migrate-deployment.ts +200 -0
  9. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  10. package/bin/apifuse-migrate-shape.ts +202 -0
  11. package/bin/apifuse-pack-check.ts +19 -6
  12. package/bin/apifuse-pack-smoke.ts +57 -2
  13. package/bin/apifuse-pack-types.ts +736 -38
  14. package/bin/apifuse-perf.ts +59 -23
  15. package/bin/apifuse-record.ts +923 -94
  16. package/bin/apifuse-submit-check.ts +2313 -281
  17. package/bin/apifuse-test.ts +3 -1
  18. package/bin/submit-check-delimited-text.ts +50 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth-turn/index.js +1 -1
  21. package/dist/auth.d.ts +14 -0
  22. package/dist/auth.js +67 -0
  23. package/dist/ceremonies/index.d.ts +16 -0
  24. package/dist/ceremonies/index.js +141 -36
  25. package/dist/cli/commands.d.ts +1 -1
  26. package/dist/cli/commands.js +30 -0
  27. package/dist/cli/create.d.ts +1 -1
  28. package/dist/cli/create.js +8 -2
  29. package/dist/cli/deployment-intent.d.ts +178 -0
  30. package/dist/cli/deployment-intent.js +277 -0
  31. package/dist/cli/local-connection.d.ts +111 -0
  32. package/dist/cli/local-connection.js +151 -0
  33. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  34. package/dist/cli/migrate-deployment-intent.js +1552 -0
  35. package/dist/cli/migrate-operation-declaration.d.ts +70 -0
  36. package/dist/cli/migrate-operation-declaration.js +2206 -0
  37. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  38. package/dist/cli/migrate-operation-shape.js +113 -0
  39. package/dist/cli/migrate-provider-shape.d.ts +56 -0
  40. package/dist/cli/migrate-provider-shape.js +580 -0
  41. package/dist/cli/resolver-runtime.d.ts +21 -0
  42. package/dist/cli/resolver-runtime.js +31 -0
  43. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  44. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  45. package/dist/cli/templates/provider/README.md.tpl +41 -19
  46. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  47. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  48. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  49. package/dist/config/loader.d.ts +202 -16
  50. package/dist/config/loader.js +491 -141
  51. package/dist/contract-serialization.js +4 -8
  52. package/dist/contract-types.d.ts +12 -5
  53. package/dist/contract.js +24 -10
  54. package/dist/declaration-validation.d.ts +34 -0
  55. package/dist/declaration-validation.js +305 -0
  56. package/dist/define.d.ts +71 -29
  57. package/dist/define.js +807 -69
  58. package/dist/dev.d.ts +5 -0
  59. package/dist/dev.js +8 -1
  60. package/dist/engine.d.ts +129 -0
  61. package/dist/engine.js +209 -0
  62. package/dist/error-observability.d.ts +7 -0
  63. package/dist/error-observability.js +61 -0
  64. package/dist/error-resolution.d.ts +18 -0
  65. package/dist/error-resolution.js +243 -0
  66. package/dist/errors.d.ts +74 -0
  67. package/dist/errors.js +99 -0
  68. package/dist/fixture-sanitization.d.ts +28 -0
  69. package/dist/fixture-sanitization.js +371 -0
  70. package/dist/handle-meta.d.ts +90 -0
  71. package/dist/handle-meta.js +73 -0
  72. package/dist/handle.d.ts +226 -0
  73. package/dist/handle.js +345 -0
  74. package/dist/health-scenario.d.ts +1901 -0
  75. package/dist/health-scenario.js +653 -0
  76. package/dist/i18n/catalog.js +21 -1
  77. package/dist/i18n/error-messages.d.ts +86 -0
  78. package/dist/i18n/error-messages.js +192 -0
  79. package/dist/i18n/index.d.ts +1 -0
  80. package/dist/i18n/index.js +1 -0
  81. package/dist/i18n/keys.d.ts +12 -0
  82. package/dist/i18n/keys.js +76 -5
  83. package/dist/i18n/operation-locale-namespace.d.ts +2 -0
  84. package/dist/i18n/operation-locale-namespace.js +7 -0
  85. package/dist/index.d.ts +40 -15
  86. package/dist/index.js +29 -11
  87. package/dist/lint.d.ts +43 -11
  88. package/dist/lint.js +2512 -177
  89. package/dist/native-address.d.ts +43 -0
  90. package/dist/native-address.js +281 -0
  91. package/dist/native-egress-policy.d.ts +31 -0
  92. package/dist/native-egress-policy.js +288 -0
  93. package/dist/observability.d.ts +5 -2
  94. package/dist/observability.js +48 -1
  95. package/dist/provider.d.ts +18 -8
  96. package/dist/provider.js +11 -6
  97. package/dist/runtime/auth-flow.d.ts +6 -1
  98. package/dist/runtime/auth-flow.js +8 -0
  99. package/dist/runtime/bounded-expiring-map.d.ts +21 -0
  100. package/dist/runtime/bounded-expiring-map.js +64 -0
  101. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  102. package/dist/runtime/browser-telemetry-binding.js +83 -0
  103. package/dist/runtime/browser-telemetry.d.ts +76 -0
  104. package/dist/runtime/browser-telemetry.js +165 -0
  105. package/dist/runtime/browser.d.ts +4 -0
  106. package/dist/runtime/browser.js +688 -83
  107. package/dist/runtime/cache-telemetry.d.ts +71 -0
  108. package/dist/runtime/cache-telemetry.js +177 -0
  109. package/dist/runtime/cache.d.ts +3 -0
  110. package/dist/runtime/cache.js +245 -23
  111. package/dist/runtime/capability-response.d.ts +3 -0
  112. package/dist/runtime/capability-response.js +56 -0
  113. package/dist/runtime/capability-telemetry.d.ts +46 -0
  114. package/dist/runtime/capability-telemetry.js +68 -0
  115. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  116. package/dist/runtime/chrome149-header-order.js +289 -0
  117. package/dist/runtime/diagnostic-env-values.d.ts +5 -0
  118. package/dist/runtime/diagnostic-env-values.js +57 -0
  119. package/dist/runtime/diagnostic-env.d.ts +14 -0
  120. package/dist/runtime/diagnostic-env.js +44 -0
  121. package/dist/runtime/diagnostic-redactor.d.ts +54 -0
  122. package/dist/runtime/diagnostic-redactor.js +571 -0
  123. package/dist/runtime/egress-lease.d.ts +65 -0
  124. package/dist/runtime/egress-lease.js +236 -0
  125. package/dist/runtime/engine-mode.d.ts +74 -0
  126. package/dist/runtime/engine-mode.js +152 -0
  127. package/dist/runtime/env.js +14 -1
  128. package/dist/runtime/executor.d.ts +3 -2
  129. package/dist/runtime/executor.js +23 -26
  130. package/dist/runtime/handle-wordlist.d.ts +43 -0
  131. package/dist/runtime/handle-wordlist.js +256 -0
  132. package/dist/runtime/handle.d.ts +71 -0
  133. package/dist/runtime/handle.js +908 -0
  134. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  135. package/dist/runtime/http-telemetry-binding.js +152 -0
  136. package/dist/runtime/http-telemetry-guard.d.ts +16 -0
  137. package/dist/runtime/http-telemetry-guard.js +202 -0
  138. package/dist/runtime/http-telemetry.d.ts +104 -0
  139. package/dist/runtime/http-telemetry.js +190 -0
  140. package/dist/runtime/http.d.ts +34 -0
  141. package/dist/runtime/http.js +680 -121
  142. package/dist/runtime/insights.js +2 -2
  143. package/dist/runtime/instrumentation.d.ts +2 -2
  144. package/dist/runtime/instrumentation.js +462 -19
  145. package/dist/runtime/native-network-errors.d.ts +33 -0
  146. package/dist/runtime/native-network-errors.js +69 -0
  147. package/dist/runtime/native-network.d.ts +105 -0
  148. package/dist/runtime/native-network.js +1433 -0
  149. package/dist/runtime/native-telemetry.d.ts +142 -0
  150. package/dist/runtime/native-telemetry.js +231 -0
  151. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  152. package/dist/runtime/ocr-telemetry.js +192 -0
  153. package/dist/runtime/ocr.d.ts +29 -0
  154. package/dist/runtime/ocr.js +468 -0
  155. package/dist/runtime/otlp.d.ts +71 -2
  156. package/dist/runtime/otlp.js +398 -16
  157. package/dist/runtime/provider.d.ts +7 -0
  158. package/dist/runtime/provider.js +14 -0
  159. package/dist/runtime/proxy-errors.js +6 -2
  160. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  161. package/dist/runtime/proxy-nodemaven.js +147 -0
  162. package/dist/runtime/proxy-retry-policy.d.ts +4 -1
  163. package/dist/runtime/proxy-retry-policy.js +11 -2
  164. package/dist/runtime/proxy-telemetry.d.ts +133 -3
  165. package/dist/runtime/proxy-telemetry.js +218 -70
  166. package/dist/runtime/redirect-hop-headers.d.ts +19 -0
  167. package/dist/runtime/redirect-hop-headers.js +58 -0
  168. package/dist/runtime/redirects.d.ts +61 -0
  169. package/dist/runtime/redirects.js +149 -0
  170. package/dist/runtime/redis.d.ts +1 -1
  171. package/dist/runtime/redis.js +4 -2
  172. package/dist/runtime/request-options.d.ts +75 -1
  173. package/dist/runtime/request-options.js +588 -0
  174. package/dist/runtime/request-telemetry.d.ts +70 -0
  175. package/dist/runtime/request-telemetry.js +229 -0
  176. package/dist/runtime/resolver-config.d.ts +13 -0
  177. package/dist/runtime/resolver-config.js +14 -0
  178. package/dist/runtime/resolver-public.d.ts +3 -0
  179. package/dist/runtime/resolver-public.js +1 -0
  180. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  181. package/dist/runtime/resolver-runtime-options.js +33 -0
  182. package/dist/runtime/resolver-shared.d.ts +24 -0
  183. package/dist/runtime/resolver-shared.js +69 -0
  184. package/dist/runtime/resolver-span-attributes.d.ts +34 -0
  185. package/dist/runtime/resolver-span-attributes.js +93 -0
  186. package/dist/runtime/resolver-telemetry.d.ts +135 -0
  187. package/dist/runtime/resolver-telemetry.js +204 -0
  188. package/dist/runtime/resolver-usage.d.ts +32 -0
  189. package/dist/runtime/resolver-usage.js +54 -0
  190. package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
  191. package/dist/runtime/resolver-vendors/bindings.js +45 -0
  192. package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
  193. package/dist/runtime/resolver-vendors/browser.js +423 -0
  194. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  195. package/dist/runtime/resolver-vendors/capsolver.js +544 -0
  196. package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
  197. package/dist/runtime/resolver-vendors/hosts.js +68 -0
  198. package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
  199. package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
  200. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  201. package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
  202. package/dist/runtime/resolver-vendors/types.d.ts +125 -0
  203. package/dist/runtime/resolver-vendors/types.js +106 -0
  204. package/dist/runtime/resolver.d.ts +79 -0
  205. package/dist/runtime/resolver.js +1094 -0
  206. package/dist/runtime/state-telemetry.d.ts +79 -0
  207. package/dist/runtime/state-telemetry.js +194 -0
  208. package/dist/runtime/state.d.ts +10 -1
  209. package/dist/runtime/state.js +322 -82
  210. package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
  211. package/dist/runtime/stealth-akamai-sbsd.js +142 -0
  212. package/dist/runtime/stealth-cookies.d.ts +24 -0
  213. package/dist/runtime/stealth-cookies.js +131 -0
  214. package/dist/runtime/stealth-owned-headers.d.ts +39 -0
  215. package/dist/runtime/stealth-owned-headers.js +57 -0
  216. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  217. package/dist/runtime/stealth-telemetry.js +179 -0
  218. package/dist/runtime/stealth.d.ts +45 -6
  219. package/dist/runtime/stealth.js +1775 -320
  220. package/dist/runtime/stt-telemetry.d.ts +77 -0
  221. package/dist/runtime/stt-telemetry.js +176 -0
  222. package/dist/runtime/stt.js +18 -19
  223. package/dist/runtime/timeout.d.ts +5 -0
  224. package/dist/runtime/timeout.js +12 -0
  225. package/dist/runtime/trace-config.d.ts +12 -0
  226. package/dist/runtime/trace-config.js +62 -0
  227. package/dist/runtime/trace.d.ts +17 -0
  228. package/dist/runtime/trace.js +176 -25
  229. package/dist/runtime-boundary-lint.d.ts +137 -0
  230. package/dist/runtime-boundary-lint.js +590 -0
  231. package/dist/schema.d.ts +20 -0
  232. package/dist/schema.js +72 -2
  233. package/dist/serve.d.ts +1 -1
  234. package/dist/serve.js +1 -1
  235. package/dist/server/error-observability.d.ts +1 -0
  236. package/dist/server/error-observability.js +1 -0
  237. package/dist/server/error-stack-frames.d.ts +35 -0
  238. package/dist/server/error-stack-frames.js +219 -0
  239. package/dist/server/index.d.ts +21 -2
  240. package/dist/server/index.js +11 -2
  241. package/dist/server/runtime-diagnostics.d.ts +165 -0
  242. package/dist/server/runtime-diagnostics.js +360 -0
  243. package/dist/server/sdk-version.d.ts +3 -0
  244. package/dist/server/sdk-version.js +19 -0
  245. package/dist/server/self-test-input-tokens.d.ts +2 -1
  246. package/dist/server/self-test-input-tokens.js +18 -14
  247. package/dist/server/self-test-redaction.js +8 -33
  248. package/dist/server/self-test.d.ts +18 -3
  249. package/dist/server/self-test.js +139 -76
  250. package/dist/server/sensitive-values.d.ts +26 -0
  251. package/dist/server/sensitive-values.js +137 -0
  252. package/dist/server/serve-implementation.d.ts +285 -0
  253. package/dist/server/serve-implementation.js +3172 -0
  254. package/dist/server/serve.d.ts +1 -75
  255. package/dist/server/serve.js +1 -1170
  256. package/dist/server/state-backend-report.d.ts +70 -0
  257. package/dist/server/state-backend-report.js +113 -0
  258. package/dist/server/trace-output.d.ts +7 -0
  259. package/dist/server/trace-output.js +111 -0
  260. package/dist/server/types.d.ts +37 -5
  261. package/dist/server/types.js +34 -1
  262. package/dist/stateful/errors.d.ts +19 -0
  263. package/dist/stateful/errors.js +24 -0
  264. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  265. package/dist/stateful/http-provider-event-emitter.js +237 -0
  266. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  267. package/dist/stateful/http-session-owner-registry.js +210 -0
  268. package/dist/stateful/index.d.ts +18 -0
  269. package/dist/stateful/index.js +18 -0
  270. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  271. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  272. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  273. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  274. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  275. package/dist/stateful/provider-event-pipeline.js +1 -0
  276. package/dist/stateful/provider-events.d.ts +101 -0
  277. package/dist/stateful/provider-events.js +289 -0
  278. package/dist/stateful/session-key.d.ts +15 -0
  279. package/dist/stateful/session-key.js +86 -0
  280. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  281. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  282. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  283. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  284. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  285. package/dist/stateful/stateful-provider-adapter.js +287 -0
  286. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  287. package/dist/stateful/stateful-provider-observability.js +161 -0
  288. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  289. package/dist/stateful/stateful-provider-owner-forwarder.js +237 -0
  290. package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
  291. package/dist/stateful/stateful-provider-runtime-context.js +61 -0
  292. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  293. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  294. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  295. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  296. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  297. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  298. package/dist/stateful-signing.d.ts +18 -0
  299. package/dist/stateful-signing.js +27 -0
  300. package/dist/stealth/profiles.d.ts +15 -4
  301. package/dist/stealth/profiles.js +169 -211
  302. package/dist/stream-evidence.d.ts +74 -0
  303. package/dist/stream-evidence.js +785 -0
  304. package/dist/stream.js +7 -1
  305. package/dist/testing/index.d.ts +2 -1
  306. package/dist/testing/index.js +2 -1
  307. package/dist/testing/run.d.ts +32 -2
  308. package/dist/testing/run.js +509 -25
  309. package/dist/trace-sanitization.d.ts +6 -0
  310. package/dist/trace-sanitization.js +68 -0
  311. package/dist/types.d.ts +899 -210
  312. package/dist/types.js +1 -0
  313. package/dist/typescript-module.d.ts +39 -0
  314. package/dist/typescript-module.js +72 -0
  315. package/package.json +52 -6
  316. package/src/auth-turn/index.ts +1 -1
  317. package/src/auth.ts +118 -0
  318. package/src/ceremonies/index.ts +189 -46
  319. package/src/cli/commands.ts +37 -0
  320. package/src/cli/create.ts +8 -2
  321. package/src/cli/deployment-intent.ts +364 -0
  322. package/src/cli/local-connection.ts +221 -0
  323. package/src/cli/migrate-deployment-intent.ts +1908 -0
  324. package/src/cli/migrate-operation-declaration.ts +3026 -0
  325. package/src/cli/migrate-operation-shape.ts +184 -0
  326. package/src/cli/migrate-provider-shape.ts +778 -0
  327. package/src/cli/resolver-runtime.ts +72 -0
  328. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  329. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  330. package/src/cli/templates/provider/README.md.tpl +41 -19
  331. package/src/cli/templates/provider/index.ts.tpl +6 -3
  332. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  333. package/src/cli/templates/provider/provider.json.tpl +6 -0
  334. package/src/config/loader.ts +774 -181
  335. package/src/contract-serialization.ts +5 -7
  336. package/src/contract-types.ts +12 -5
  337. package/src/contract.ts +24 -10
  338. package/src/declaration-validation.ts +376 -0
  339. package/src/define.ts +1105 -131
  340. package/src/dev.ts +13 -1
  341. package/src/engine.ts +390 -0
  342. package/src/error-observability.ts +64 -0
  343. package/src/error-resolution.ts +269 -0
  344. package/src/errors.ts +157 -0
  345. package/src/fixture-sanitization.ts +422 -0
  346. package/src/handle-meta.ts +143 -0
  347. package/src/handle.ts +694 -0
  348. package/src/health-scenario.ts +960 -0
  349. package/src/i18n/catalog.ts +23 -1
  350. package/src/i18n/error-messages.ts +255 -0
  351. package/src/i18n/index.ts +1 -0
  352. package/src/i18n/keys.ts +80 -6
  353. package/src/i18n/operation-locale-namespace.ts +10 -0
  354. package/src/index.ts +390 -23
  355. package/src/lint.ts +2975 -234
  356. package/src/native-address.ts +340 -0
  357. package/src/native-egress-policy.ts +358 -0
  358. package/src/observability.ts +51 -1
  359. package/src/provider.ts +207 -27
  360. package/src/runtime/auth-flow.ts +15 -0
  361. package/src/runtime/bounded-expiring-map.ts +67 -0
  362. package/src/runtime/browser-telemetry-binding.ts +92 -0
  363. package/src/runtime/browser-telemetry.ts +289 -0
  364. package/src/runtime/browser.ts +882 -99
  365. package/src/runtime/cache-telemetry.ts +247 -0
  366. package/src/runtime/cache.ts +258 -22
  367. package/src/runtime/capability-response.ts +53 -0
  368. package/src/runtime/capability-telemetry.ts +101 -0
  369. package/src/runtime/chrome149-header-order.ts +330 -0
  370. package/src/runtime/diagnostic-env-values.ts +56 -0
  371. package/src/runtime/diagnostic-env.ts +65 -0
  372. package/src/runtime/diagnostic-redactor.ts +668 -0
  373. package/src/runtime/egress-lease.ts +341 -0
  374. package/src/runtime/engine-mode.ts +233 -0
  375. package/src/runtime/env.ts +15 -1
  376. package/src/runtime/executor.ts +39 -32
  377. package/src/runtime/handle-wordlist.ts +266 -0
  378. package/src/runtime/handle.ts +1180 -0
  379. package/src/runtime/http-telemetry-binding.ts +162 -0
  380. package/src/runtime/http-telemetry-guard.ts +208 -0
  381. package/src/runtime/http-telemetry.ts +292 -0
  382. package/src/runtime/http.ts +896 -150
  383. package/src/runtime/insights.ts +2 -2
  384. package/src/runtime/instrumentation.ts +628 -28
  385. package/src/runtime/native-network-errors.ts +99 -0
  386. package/src/runtime/native-network.ts +1853 -0
  387. package/src/runtime/native-telemetry.ts +366 -0
  388. package/src/runtime/ocr-telemetry.ts +308 -0
  389. package/src/runtime/ocr.ts +564 -0
  390. package/src/runtime/otlp.ts +468 -21
  391. package/src/runtime/provider.ts +15 -0
  392. package/src/runtime/proxy-errors.ts +12 -4
  393. package/src/runtime/proxy-nodemaven.ts +222 -0
  394. package/src/runtime/proxy-retry-policy.ts +12 -1
  395. package/src/runtime/proxy-telemetry.ts +358 -103
  396. package/src/runtime/redirect-hop-headers.ts +71 -0
  397. package/src/runtime/redirects.ts +215 -0
  398. package/src/runtime/redis.ts +7 -2
  399. package/src/runtime/request-options.ts +728 -1
  400. package/src/runtime/request-telemetry.ts +375 -0
  401. package/src/runtime/resolver-config.ts +18 -0
  402. package/src/runtime/resolver-public.ts +44 -0
  403. package/src/runtime/resolver-runtime-options.ts +50 -0
  404. package/src/runtime/resolver-shared.ts +113 -0
  405. package/src/runtime/resolver-span-attributes.ts +139 -0
  406. package/src/runtime/resolver-telemetry.ts +362 -0
  407. package/src/runtime/resolver-usage.ts +99 -0
  408. package/src/runtime/resolver-vendors/bindings.ts +61 -0
  409. package/src/runtime/resolver-vendors/browser.ts +591 -0
  410. package/src/runtime/resolver-vendors/capsolver.ts +727 -0
  411. package/src/runtime/resolver-vendors/hosts.ts +81 -0
  412. package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
  413. package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
  414. package/src/runtime/resolver-vendors/types.ts +261 -0
  415. package/src/runtime/resolver.ts +1543 -0
  416. package/src/runtime/state-telemetry.ts +298 -0
  417. package/src/runtime/state.ts +435 -89
  418. package/src/runtime/stealth-akamai-sbsd.ts +207 -0
  419. package/src/runtime/stealth-cookies.ts +155 -0
  420. package/src/runtime/stealth-owned-headers.ts +67 -0
  421. package/src/runtime/stealth-telemetry.ts +309 -0
  422. package/src/runtime/stealth.ts +2275 -372
  423. package/src/runtime/stt-telemetry.ts +282 -0
  424. package/src/runtime/stt.ts +96 -91
  425. package/src/runtime/timeout.ts +18 -0
  426. package/src/runtime/trace-config.ts +78 -0
  427. package/src/runtime/trace.ts +257 -35
  428. package/src/runtime-boundary-lint.ts +707 -0
  429. package/src/schema.ts +80 -2
  430. package/src/serve.ts +6 -1
  431. package/src/server/error-observability.ts +1 -0
  432. package/src/server/error-stack-frames.ts +214 -0
  433. package/src/server/index.ts +184 -2
  434. package/src/server/runtime-diagnostics.ts +522 -0
  435. package/src/server/sdk-version.ts +22 -0
  436. package/src/server/self-test-input-tokens.ts +29 -14
  437. package/src/server/self-test-redaction.ts +10 -40
  438. package/src/server/self-test.ts +205 -89
  439. package/src/server/sensitive-values.ts +211 -0
  440. package/src/server/serve-implementation.ts +4596 -0
  441. package/src/server/serve.ts +1 -1685
  442. package/src/server/state-backend-report.ts +185 -0
  443. package/src/server/trace-output.ts +189 -0
  444. package/src/server/types.ts +34 -1
  445. package/src/stateful/README.md +146 -0
  446. package/src/stateful/errors.ts +35 -0
  447. package/src/stateful/http-provider-event-emitter.ts +314 -0
  448. package/src/stateful/http-session-owner-registry.ts +306 -0
  449. package/src/stateful/index.ts +18 -0
  450. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  451. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  452. package/src/stateful/provider-event-pipeline.ts +61 -0
  453. package/src/stateful/provider-events.ts +462 -0
  454. package/src/stateful/session-key.ts +111 -0
  455. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  456. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  457. package/src/stateful/stateful-provider-adapter.ts +562 -0
  458. package/src/stateful/stateful-provider-observability.ts +261 -0
  459. package/src/stateful/stateful-provider-owner-forwarder.ts +310 -0
  460. package/src/stateful/stateful-provider-runtime-context.ts +96 -0
  461. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  462. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  463. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  464. package/src/stateful-signing.ts +46 -0
  465. package/src/stealth/profiles.ts +212 -231
  466. package/src/stream-evidence.ts +988 -0
  467. package/src/stream.ts +8 -1
  468. package/src/testing/index.ts +10 -1
  469. package/src/testing/run.ts +685 -19
  470. package/src/trace-sanitization.ts +110 -0
  471. package/src/types.ts +1014 -273
  472. package/src/typescript-module.ts +100 -0
  473. package/dist/choice-token.d.ts +0 -24
  474. package/dist/choice-token.js +0 -74
  475. package/dist/runtime/choice.d.ts +0 -15
  476. package/dist/runtime/choice.js +0 -413
  477. package/src/choice-token.ts +0 -165
  478. package/src/runtime/choice.ts +0 -686
@@ -1,3 +1,19 @@
1
+ import { readDiagnosticEnv, registerDiagnosticValue } from "./diagnostic-env.js";
2
+ import { AsyncResource } from "node:async_hooks";
3
+ import { observeTelemetryCallback as observeBrowserTelemetry } from "./http-telemetry-guard.js";
4
+ import { cookieValuesFromHeaders } from "./stealth-cookies.js";
5
+ function browserTelemetryErrorCode(error) {
6
+ const code = error && typeof error === "object" && "code" in error
7
+ ? error.code
8
+ : undefined;
9
+ if (code === "BROWSER_CDP_POOL_REQUIRED" ||
10
+ code === "BROWSER_PROXY_INVALID" ||
11
+ code === "BROWSER_RUNTIME_UNSUPPORTED")
12
+ return code;
13
+ if (typeof code === "number")
14
+ return "BROWSER_CDP_POOL_ERROR";
15
+ return "other";
16
+ }
1
17
  import { createRequire } from "node:module";
2
18
  import { ProviderError } from "../errors.js";
3
19
  const require = createRequire(import.meta.url);
@@ -15,7 +31,7 @@ function toResourceBody(body) {
15
31
  return Buffer.from(body);
16
32
  }
17
33
  function isResourceMethod(method) {
18
- return method === "GET" || method === "HEAD";
34
+ return method === "GET" || method === "HEAD" || method === "POST";
19
35
  }
20
36
  async function toResourceRequest(request) {
21
37
  const method = request.method().toUpperCase();
@@ -59,6 +75,68 @@ function getCdpPausedRequestId(params) {
59
75
  }
60
76
  return params.requestId;
61
77
  }
78
+ function isCdpResponseStage(params) {
79
+ return (isRecord(params) &&
80
+ (typeof params.responseStatusCode === "number" ||
81
+ typeof params.responseErrorReason === "string"));
82
+ }
83
+ function toCdpPausedDocumentResponse(params) {
84
+ if (!isRecord(params) ||
85
+ typeof params.requestId !== "string" ||
86
+ typeof params.responseStatusCode !== "number" ||
87
+ params.resourceType !== "Document" ||
88
+ !isRecord(params.request)) {
89
+ return null;
90
+ }
91
+ const responseCode = params.responseStatusCode;
92
+ const responseHeaders = Array.isArray(params.responseHeaders)
93
+ ? params.responseHeaders.flatMap((header) => {
94
+ if (!isRecord(header) ||
95
+ typeof header.name !== "string" ||
96
+ typeof header.value !== "string") {
97
+ return [];
98
+ }
99
+ return [{ name: header.name, value: header.value }];
100
+ })
101
+ : [];
102
+ return {
103
+ hasBody: String(params.request.method ?? "").toUpperCase() !== "HEAD" &&
104
+ responseCode >= 200 &&
105
+ (responseCode < 300 || responseCode >= 400) &&
106
+ responseCode !== 204 &&
107
+ responseCode !== 205 &&
108
+ responseCode !== 304,
109
+ requestId: params.requestId,
110
+ responseCode,
111
+ responseHeaders,
112
+ };
113
+ }
114
+ function toCdpDocumentFulfillParams(response, body, contentSecurityPolicy) {
115
+ const responseBody = typeof body.body === "string" ? body.body : "";
116
+ return {
117
+ body: body.base64Encoded ? responseBody : Buffer.from(responseBody).toString("base64"),
118
+ requestId: response.requestId,
119
+ responseCode: response.responseCode,
120
+ responseHeaders: [
121
+ ...response.responseHeaders,
122
+ { name: "Content-Security-Policy", value: contentSecurityPolicy },
123
+ ],
124
+ };
125
+ }
126
+ function resourcePolicyFetchPatterns(policy) {
127
+ return [
128
+ { requestStage: "Request", urlPattern: "*" },
129
+ ...(policy.documentContentSecurityPolicy
130
+ ? [
131
+ {
132
+ requestStage: "Response",
133
+ resourceType: "Document",
134
+ urlPattern: "*",
135
+ },
136
+ ]
137
+ : []),
138
+ ];
139
+ }
62
140
  function toCdpResourceHeaders(value) {
63
141
  if (!isRecord(value)) {
64
142
  return {};
@@ -104,8 +182,129 @@ async function fulfillResourceRoute(route, decision) {
104
182
  status: decision.status ?? 200,
105
183
  });
106
184
  }
185
+ async function decideResourceRequest(policy, request) {
186
+ for (const resourceRoute of policy.routes) {
187
+ if (matchesResourceRoute(resourceRoute.match, request)) {
188
+ return await resourceRoute.handle(request);
189
+ }
190
+ }
191
+ return { action: "block" };
192
+ }
193
+ async function abortResourceRoute(route) {
194
+ await route.abort("blockedbyclient");
195
+ }
196
+ async function handleCdpResourceRequest(session, policy, allowedMethods, params) {
197
+ const paused = toCdpResourceRequest(params);
198
+ const requestId = paused?.requestId ?? getCdpPausedRequestId(params);
199
+ if (!requestId)
200
+ return;
201
+ try {
202
+ if (!paused || !allowedMethods.has(paused.request.method)) {
203
+ await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
204
+ return;
205
+ }
206
+ const decision = await decideResourceRequest(policy, paused.request);
207
+ switch (decision.action) {
208
+ case "continue":
209
+ await session.send("Fetch.continueRequest", { requestId });
210
+ return;
211
+ case "fulfill":
212
+ await session.send("Fetch.fulfillRequest", toCdpFulfillParams(requestId, decision));
213
+ return;
214
+ case "block":
215
+ await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
216
+ return;
217
+ }
218
+ }
219
+ catch {
220
+ await session
221
+ .send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
222
+ .catch(() => undefined);
223
+ }
224
+ }
225
+ async function handleCdpDocumentResponse(session, contentSecurityPolicy, params) {
226
+ const requestId = getCdpPausedRequestId(params);
227
+ if (!requestId)
228
+ return;
229
+ try {
230
+ const response = toCdpPausedDocumentResponse(params);
231
+ if (!response)
232
+ throw new Error("CDP document response metadata is unavailable");
233
+ if (!response.hasBody) {
234
+ await session.send("Fetch.continueResponse", {
235
+ requestId,
236
+ responseCode: response.responseCode,
237
+ responseHeaders: [...response.responseHeaders],
238
+ });
239
+ return;
240
+ }
241
+ const body = await session.send("Fetch.getResponseBody", {
242
+ requestId: response.requestId,
243
+ });
244
+ await session.send("Fetch.fulfillRequest", toCdpDocumentFulfillParams(response, body, contentSecurityPolicy));
245
+ }
246
+ catch {
247
+ await session
248
+ .send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
249
+ .catch(() => undefined);
250
+ }
251
+ }
252
+ function getCdpAuthRequiredRequestId(params) {
253
+ if (!isRecord(params) || typeof params.requestId !== "string") {
254
+ return null;
255
+ }
256
+ return params.requestId;
257
+ }
258
+ function isCdpProxyAuthChallenge(params) {
259
+ return (isRecord(params) && isRecord(params.authChallenge) && params.authChallenge.source === "Proxy");
260
+ }
261
+ async function handleCdpAuthRequired(session, proxy, authAttempts, params, telemetry) {
262
+ const requestId = getCdpAuthRequiredRequestId(params);
263
+ if (!requestId)
264
+ return;
265
+ // Chromium can emit another challenge after rejected credentials. Cancel a
266
+ // retry so incorrect credentials fail promptly instead of looping forever.
267
+ const response = isCdpProxyAuthChallenge(params) && !authAttempts.has(requestId)
268
+ ? (() => {
269
+ authAttempts.add(requestId);
270
+ observeBrowserTelemetry(telemetry, () => telemetry?.recordProxyAuthChallenge());
271
+ return {
272
+ authChallengeResponse: {
273
+ password: proxy.password,
274
+ response: "ProvideCredentials",
275
+ username: proxy.username,
276
+ },
277
+ requestId,
278
+ };
279
+ })()
280
+ : {
281
+ authChallengeResponse: { response: "CancelAuth" },
282
+ requestId,
283
+ };
284
+ await session.send("Fetch.continueWithAuth", response).catch(() => undefined);
285
+ }
286
+ function createResourcePolicyHandler(policy, allowedMethods) {
287
+ return async (route) => {
288
+ const request = await toResourceRequest(route.request());
289
+ if (!request || !allowedMethods.has(request.method)) {
290
+ await abortResourceRoute(route);
291
+ return;
292
+ }
293
+ const decision = await decideResourceRequest(policy, request);
294
+ switch (decision.action) {
295
+ case "continue":
296
+ await route.continue();
297
+ return;
298
+ case "fulfill":
299
+ await fulfillResourceRoute(route, decision);
300
+ return;
301
+ case "block":
302
+ await abortResourceRoute(route);
303
+ }
304
+ };
305
+ }
107
306
  function getDefaultCdpPoolUrl(env = process.env) {
108
- return env.APIFUSE__CDP_POOL__URL;
307
+ return readDiagnosticEnv("APIFUSE__CDP_POOL__URL", env);
109
308
  }
110
309
  async function importOptionalModule(moduleName) {
111
310
  return (await import(moduleName));
@@ -139,12 +338,62 @@ function formatExpression(fn) {
139
338
  }
140
339
  return `(${fn.toString()})()`;
141
340
  }
142
- function toLaunchOptions(options) {
341
+ function toLaunchOptions(options, proxy) {
143
342
  return {
144
- args: options.extraArgs,
343
+ // `extraArgs` is optional, but playwright-extra's stealth evasions mutate
344
+ // `options.args` unguarded (navigator.webdriver does
345
+ // `options.args.findIndex(...)` in beforeLaunch). Forwarding `undefined`
346
+ // therefore crashes every stealth launch that omits extraArgs with
347
+ // "TypeError: undefined is not an object (evaluating 'options.args.findIndex')".
348
+ // Always hand the launcher a concrete array.
349
+ args: options.extraArgs ?? [],
145
350
  executablePath: options.executablePath,
146
351
  headless: options.headless ?? true,
147
- proxy: options.proxy ? { server: options.proxy } : undefined,
352
+ proxy,
353
+ };
354
+ }
355
+ function hasProxyCredentials(proxy) {
356
+ return proxy?.username !== undefined && proxy.password !== undefined;
357
+ }
358
+ function toPlaywrightProxy(proxy) {
359
+ if (!proxy)
360
+ return undefined;
361
+ const schemeEnd = proxy.indexOf("://");
362
+ const authorityStart = schemeEnd >= 0 ? schemeEnd + 3 : 0;
363
+ const remainder = proxy.slice(authorityStart);
364
+ const authorityEnd = remainder.search(/[/?#]/);
365
+ const authority = remainder.slice(0, authorityEnd >= 0 ? authorityEnd : undefined);
366
+ if (!authority.includes("@")) {
367
+ return { server: proxy };
368
+ }
369
+ let parsed;
370
+ try {
371
+ parsed = new URL(proxy);
372
+ }
373
+ catch {
374
+ throw new ProviderError("Browser proxy URL is invalid", {
375
+ code: "BROWSER_PROXY_INVALID",
376
+ fix: "Use a valid proxy URL.",
377
+ });
378
+ }
379
+ let username;
380
+ let password;
381
+ try {
382
+ username = decodeURIComponent(parsed.username);
383
+ password = decodeURIComponent(parsed.password);
384
+ }
385
+ catch {
386
+ throw new ProviderError("Browser proxy credentials use invalid percent-encoding", {
387
+ code: "BROWSER_PROXY_INVALID",
388
+ fix: "Percent-encode the proxy username and password as URL userinfo.",
389
+ });
390
+ }
391
+ registerDiagnosticValue(username);
392
+ registerDiagnosticValue(password);
393
+ return {
394
+ server: `${parsed.protocol}//${parsed.host}`,
395
+ username,
396
+ password,
148
397
  };
149
398
  }
150
399
  class PlaywrightBrowserLocator {
@@ -309,13 +558,25 @@ async function loadSeleniumBase() {
309
558
  }
310
559
  class PlaywrightBrowserPage {
311
560
  page;
561
+ proxy;
562
+ telemetry;
312
563
  id = "main";
313
564
  pageId;
314
- constructor(page) {
565
+ constructor(page, proxy = undefined, telemetry) {
315
566
  this.page = page;
316
- }
317
- async goto(url) {
318
- await this.page.goto(url);
567
+ this.proxy = proxy;
568
+ this.telemetry = telemetry;
569
+ }
570
+ async goto(url, options) {
571
+ const response = await this.page.goto(url, options);
572
+ const headers = await response?.headersArray().catch(() => []);
573
+ for (const header of headers ?? []) {
574
+ if (header.name.toLowerCase() === "set-cookie") {
575
+ registerDiagnosticValue(header.value);
576
+ for (const value of cookieValuesFromHeaders(header.value))
577
+ registerDiagnosticValue(value);
578
+ }
579
+ }
319
580
  }
320
581
  async evaluate(fn) {
321
582
  if (typeof fn === "string") {
@@ -323,6 +584,9 @@ class PlaywrightBrowserPage {
323
584
  }
324
585
  return await this.page.evaluate(fn);
325
586
  }
587
+ async userAgent() {
588
+ return await this.evaluate("navigator.userAgent");
589
+ }
326
590
  async waitForSelector(selector, options) {
327
591
  await this.page.waitForSelector(selector, options);
328
592
  }
@@ -357,42 +621,110 @@ class PlaywrightBrowserPage {
357
621
  async close() {
358
622
  await this.page.close();
359
623
  }
624
+ async cookies() {
625
+ const cookies = (await this.page.context().cookies()).map(toBrowserCookie);
626
+ for (const cookie of cookies)
627
+ registerDiagnosticValue(cookie.value);
628
+ return cookies;
629
+ }
360
630
  async withResourcePolicy(policy, run) {
361
631
  const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
632
+ const policyHandler = createResourcePolicyHandler(policy, allowedMethods);
633
+ const context = this.page.context();
362
634
  const handler = async (route) => {
363
- const request = await toResourceRequest(route.request());
364
- if (!request || !allowedMethods.has(request.method)) {
365
- await route.abort("blockedbyclient");
635
+ try {
636
+ // Context routing sees a popup's first request before the late `page`
637
+ // event. Fail every secondary page closed so no redirect can escape
638
+ // the per-page CDP interception while that session is being attached.
639
+ if (route.request().frame().page() !== this.page) {
640
+ await abortResourceRoute(route);
641
+ return;
642
+ }
643
+ }
644
+ catch {
645
+ await abortResourceRoute(route);
366
646
  return;
367
647
  }
368
- for (const resourceRoute of policy.routes) {
369
- if (!matchesResourceRoute(resourceRoute.match, request)) {
370
- continue;
648
+ await policyHandler(route);
649
+ };
650
+ const sessions = new Set();
651
+ const pendingAttachments = new Set();
652
+ const proxy = hasProxyCredentials(this.proxy) ? this.proxy : undefined;
653
+ let active = true;
654
+ const attachPage = async (page) => {
655
+ const session = await context.newCDPSession(page);
656
+ if (!active) {
657
+ await session.detach();
658
+ return;
659
+ }
660
+ const onRequestPaused = (params) => {
661
+ if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
662
+ void handleCdpDocumentResponse(session, policy.documentContentSecurityPolicy, params);
663
+ return;
371
664
  }
372
- const decision = await resourceRoute.handle(request);
373
- switch (decision.action) {
374
- case "fulfill":
375
- await fulfillResourceRoute(route, decision);
376
- return;
377
- case "block":
378
- await route.abort("blockedbyclient");
379
- return;
665
+ void handleCdpResourceRequest(session, policy, allowedMethods, params);
666
+ };
667
+ const authAttempts = new Set();
668
+ const onAuthRequired = (params) => {
669
+ if (!proxy)
670
+ return;
671
+ void handleCdpAuthRequired(session, proxy, authAttempts, params, this.telemetry);
672
+ };
673
+ session.on("Fetch.requestPaused", onRequestPaused);
674
+ if (proxy) {
675
+ session.on("Fetch.authRequired", onAuthRequired);
676
+ }
677
+ try {
678
+ await session.send("Fetch.enable", {
679
+ ...(proxy ? { handleAuthRequests: true } : {}),
680
+ patterns: resourcePolicyFetchPatterns(policy),
681
+ });
682
+ sessions.add(session);
683
+ }
684
+ catch (error) {
685
+ session.off("Fetch.requestPaused", onRequestPaused);
686
+ if (proxy) {
687
+ session.off("Fetch.authRequired", onAuthRequired);
380
688
  }
689
+ await session.detach().catch(() => undefined);
690
+ throw error;
381
691
  }
382
- await route.abort("blockedbyclient");
383
692
  };
384
- await this.page.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
693
+ const onPage = (page) => {
694
+ const attachment = attachPage(page)
695
+ .catch(async () => {
696
+ await page.close().catch(() => undefined);
697
+ })
698
+ .finally(() => pendingAttachments.delete(attachment));
699
+ pendingAttachments.add(attachment);
700
+ };
701
+ await context.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
385
702
  try {
386
- return await run();
703
+ context.on("page", onPage);
704
+ try {
705
+ await attachPage(this.page);
706
+ return await run();
707
+ }
708
+ finally {
709
+ context.off("page", onPage);
710
+ }
387
711
  }
388
712
  finally {
389
- await this.page.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
713
+ active = false;
714
+ await context.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
715
+ await Promise.allSettled(pendingAttachments);
716
+ await Promise.all([...sessions].map(async (session) => {
717
+ await session.send("Fetch.disable").catch(() => undefined);
718
+ await session.detach().catch(() => undefined);
719
+ }));
390
720
  }
391
721
  }
392
722
  }
393
723
  class PlaywrightBrowserClient {
394
724
  options;
395
725
  browser = null;
726
+ parsedProxy;
727
+ proxyParsed = false;
396
728
  engine = "playwright-stealth";
397
729
  constructor(options = {}) {
398
730
  this.options = options;
@@ -402,13 +734,19 @@ class PlaywrightBrowserClient {
402
734
  return this.browser;
403
735
  }
404
736
  const chromium = await loadChromiumLauncher(this.options);
405
- this.browser = await chromium.launch(toLaunchOptions(this.options));
737
+ if (!this.proxyParsed) {
738
+ this.parsedProxy = toPlaywrightProxy(this.options.proxy);
739
+ this.proxyParsed = true;
740
+ }
741
+ this.browser = await chromium.launch(toLaunchOptions(this.options, this.parsedProxy));
406
742
  return this.browser;
407
743
  }
408
744
  async newPage() {
745
+ if (!this.options.cdpUrl)
746
+ observeBrowserTelemetry(this.options.telemetry, () => this.options.telemetry?.recordPoolAcquire("not_configured"));
409
747
  const browser = await this.ensureBrowser();
410
748
  const page = await browser.newPage();
411
- return new PlaywrightBrowserPage(page);
749
+ return new PlaywrightBrowserPage(page, this.parsedProxy, this.options.telemetry);
412
750
  }
413
751
  async rawPage() {
414
752
  throw new ProviderError("ctx.browser.rawPage() requires a CDP pool", {
@@ -417,10 +755,12 @@ class PlaywrightBrowserClient {
417
755
  });
418
756
  }
419
757
  async withIsolatedContext(handler) {
758
+ if (!this.options.cdpUrl)
759
+ observeBrowserTelemetry(this.options.telemetry, () => this.options.telemetry?.recordPoolAcquire("not_configured"));
420
760
  const browser = await this.ensureBrowser();
421
- const context = await browser.newContext();
761
+ const context = await browser.newContext({ serviceWorkers: this.options.serviceWorkers });
422
762
  const page = await context.newPage();
423
- const browserPage = new PlaywrightBrowserPage(page);
763
+ const browserPage = new PlaywrightBrowserPage(page, this.parsedProxy, this.options.telemetry);
424
764
  try {
425
765
  return await handler(browserPage);
426
766
  }
@@ -457,13 +797,14 @@ function normalizeWebSocketEndpoint(endpoint) {
457
797
  }
458
798
  throw new Error(`Unsupported WebSocket endpoint protocol: ${url.protocol}`);
459
799
  }
460
- class JsonRpcWebSocketClient {
800
+ class WebSocketCommandClient {
461
801
  nextId = 1;
462
802
  endpoint;
463
803
  listeners = new Map();
464
804
  pending = new Map();
465
805
  socket;
466
806
  socketPromise;
807
+ closed = false;
467
808
  constructor(endpoint) {
468
809
  this.endpoint = normalizeWebSocketEndpoint(endpoint);
469
810
  }
@@ -480,18 +821,16 @@ class JsonRpcWebSocketClient {
480
821
  }
481
822
  async send(method, params = {}) {
482
823
  const socket = await this.getSocket();
824
+ if (this.closed)
825
+ throw new Error(`WebSocket closed: ${this.endpoint}`);
483
826
  const id = this.nextId++;
484
827
  return await new Promise((resolve, reject) => {
485
828
  this.pending.set(id, { resolve, reject });
486
- socket.send(JSON.stringify({
487
- id,
488
- jsonrpc: "2.0",
489
- method,
490
- params,
491
- }));
829
+ socket.send(JSON.stringify(this.createCommandFrame(id, method, params)));
492
830
  });
493
831
  }
494
832
  async close() {
833
+ this.closed = true;
495
834
  for (const pending of this.pending.values()) {
496
835
  pending.reject(new Error(`WebSocket closed: ${this.endpoint}`));
497
836
  }
@@ -502,6 +841,8 @@ class JsonRpcWebSocketClient {
502
841
  this.socketPromise = undefined;
503
842
  }
504
843
  async getSocket() {
844
+ if (this.closed)
845
+ throw new Error(`WebSocket closed: ${this.endpoint}`);
505
846
  if (this.socket?.readyState === WebSocket.OPEN) {
506
847
  return this.socket;
507
848
  }
@@ -511,6 +852,11 @@ class JsonRpcWebSocketClient {
511
852
  this.socketPromise = new Promise((resolve, reject) => {
512
853
  const socket = new WebSocket(this.endpoint);
513
854
  socket.addEventListener("open", () => {
855
+ if (this.closed) {
856
+ socket.close();
857
+ reject(new Error(`WebSocket closed: ${this.endpoint}`));
858
+ return;
859
+ }
514
860
  this.socket = socket;
515
861
  resolve(socket);
516
862
  });
@@ -526,7 +872,11 @@ class JsonRpcWebSocketClient {
526
872
  }
527
873
  this.pending.delete(payload.id);
528
874
  if (payload.error) {
529
- pending.reject(new Error(payload.error.message ?? "JSON-RPC command failed"));
875
+ const error = new Error(payload.error.message ?? "JSON-RPC command failed");
876
+ if (typeof payload.error.code === "number") {
877
+ Object.assign(error, { code: payload.error.code });
878
+ }
879
+ pending.reject(error);
530
880
  return;
531
881
  }
532
882
  pending.resolve(payload.result ?? {});
@@ -554,6 +904,26 @@ class JsonRpcWebSocketClient {
554
904
  return this.socketPromise;
555
905
  }
556
906
  }
907
+ class CdpWebSocketClient extends WebSocketCommandClient {
908
+ sessionId;
909
+ constructor(endpoint, sessionId) {
910
+ super(endpoint);
911
+ this.sessionId = sessionId;
912
+ }
913
+ createCommandFrame(id, method, params) {
914
+ return {
915
+ id,
916
+ method,
917
+ params,
918
+ ...(this.sessionId === undefined ? {} : { sessionId: this.sessionId }),
919
+ };
920
+ }
921
+ }
922
+ class JsonRpcWebSocketClient extends WebSocketCommandClient {
923
+ createCommandFrame(id, method, params) {
924
+ return { id, jsonrpc: "2.0", method, params };
925
+ }
926
+ }
557
927
  function flattenCdpFrameTree(node, out = []) {
558
928
  if (!node) {
559
929
  return out;
@@ -567,6 +937,48 @@ function flattenCdpFrameTree(node, out = []) {
567
937
  function isRecord(value) {
568
938
  return value !== null && typeof value === "object" && !Array.isArray(value);
569
939
  }
940
+ function isBrowserCookieSameSite(value) {
941
+ return value === "Strict" || value === "Lax" || value === "None";
942
+ }
943
+ function toBrowserCookie(cookie) {
944
+ return {
945
+ name: cookie.name,
946
+ value: cookie.value,
947
+ domain: cookie.domain,
948
+ path: cookie.path,
949
+ ...(cookie.expires !== undefined && cookie.expires > 0 ? { expires: cookie.expires } : {}),
950
+ httpOnly: cookie.httpOnly,
951
+ secure: cookie.secure,
952
+ ...(isBrowserCookieSameSite(cookie.sameSite) ? { sameSite: cookie.sameSite } : {}),
953
+ };
954
+ }
955
+ function parseCdpCookies(value) {
956
+ if (!Array.isArray(value)) {
957
+ throw new Error("CDP Network.getCookies returned an invalid cookie list");
958
+ }
959
+ return value.map((cookie) => {
960
+ if (!isRecord(cookie) ||
961
+ typeof cookie.name !== "string" ||
962
+ typeof cookie.value !== "string" ||
963
+ typeof cookie.domain !== "string" ||
964
+ typeof cookie.path !== "string" ||
965
+ typeof cookie.expires !== "number" ||
966
+ typeof cookie.httpOnly !== "boolean" ||
967
+ typeof cookie.secure !== "boolean") {
968
+ throw new Error("CDP Network.getCookies returned an invalid cookie");
969
+ }
970
+ return toBrowserCookie({
971
+ name: cookie.name,
972
+ value: cookie.value,
973
+ domain: cookie.domain,
974
+ path: cookie.path,
975
+ expires: cookie.expires,
976
+ httpOnly: cookie.httpOnly,
977
+ secure: cookie.secure,
978
+ sameSite: cookie.sameSite,
979
+ });
980
+ });
981
+ }
570
982
  function parsePoolAcquireResponse(value) {
571
983
  if (!isRecord(value) ||
572
984
  typeof value.pageId !== "string" ||
@@ -586,6 +998,33 @@ function parsePoolAcquireResponse(value) {
586
998
  wsEndpoint: value.wsEndpoint,
587
999
  };
588
1000
  }
1001
+ function poolAcquireOutcome(error) {
1002
+ const code = error instanceof Error && typeof error.code === "number"
1003
+ ? error.code
1004
+ : undefined;
1005
+ switch (code) {
1006
+ case -32001:
1007
+ return "queue_full";
1008
+ case -32002:
1009
+ return "timed_out";
1010
+ case -32003:
1011
+ return "shutting_down";
1012
+ case -32004:
1013
+ return "unknown_lease";
1014
+ case -32005:
1015
+ return "unknown_method";
1016
+ case -32006:
1017
+ return "missing_allowed_hosts";
1018
+ }
1019
+ if (error instanceof Error && /Unable to connect|WebSocket closed/.test(error.message))
1020
+ return "transport_failure";
1021
+ return "other";
1022
+ }
1023
+ function poolAcquireCode(error) {
1024
+ return error instanceof Error && typeof error.code === "number"
1025
+ ? error.code
1026
+ : undefined;
1027
+ }
589
1028
  function parseCdpFrameTreeNode(value) {
590
1029
  if (!isRecord(value) || !isRecord(value.frame)) {
591
1030
  return undefined;
@@ -620,6 +1059,15 @@ function getCdpExecutionContext(params) {
620
1059
  id: typeof contextId === "number" ? contextId : undefined,
621
1060
  };
622
1061
  }
1062
+ function getCdpDestroyedExecutionContextId(params) {
1063
+ if (!isRecord(params)) {
1064
+ return undefined;
1065
+ }
1066
+ return typeof params.executionContextId === "number" ? params.executionContextId : undefined;
1067
+ }
1068
+ function isMissingExecutionContextError(error) {
1069
+ return error instanceof Error && /\b(?:cannot|failed to) find context\b/i.test(error.message);
1070
+ }
623
1071
  class CdpBrowserLocator {
624
1072
  frame;
625
1073
  selector;
@@ -711,7 +1159,7 @@ class CdpPoolBrowserPage {
711
1159
  browserContextId;
712
1160
  pageClient;
713
1161
  release;
714
- closed = false;
1162
+ closePromise;
715
1163
  initialized = false;
716
1164
  frameExecutionContexts = new Map();
717
1165
  constructor(pageId, browserContextId, pageClient, release) {
@@ -723,16 +1171,22 @@ class CdpPoolBrowserPage {
723
1171
  get id() {
724
1172
  return this.pageId;
725
1173
  }
726
- async goto(url) {
1174
+ async goto(url, options) {
727
1175
  await this.initialize();
728
1176
  const startedAt = Date.now();
729
- let loadEventSeen = false;
730
- const unsubscribe = this.pageClient.on("Page.loadEventFired", () => {
731
- loadEventSeen = true;
1177
+ const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
1178
+ const waitUntil = options?.waitUntil ?? "load";
1179
+ let expectedEventSeen = false;
1180
+ const eventName = waitUntil === "domcontentloaded" ? "Page.domContentEventFired" : "Page.loadEventFired";
1181
+ const unsubscribe = this.pageClient.on(eventName, () => {
1182
+ expectedEventSeen = true;
732
1183
  });
733
1184
  try {
734
- await this.pageClient.send("Page.navigate", { url });
735
- await this.waitForDocumentReady(startedAt + DEFAULT_WAIT_TIMEOUT_MS, () => loadEventSeen);
1185
+ const navigation = (await this.pageClient.send("Page.navigate", { url }));
1186
+ if (typeof navigation.errorText === "string" && navigation.errorText.length > 0) {
1187
+ throw new Error(`Page.navigate failed: ${navigation.errorText} at ${url}`);
1188
+ }
1189
+ await this.waitForDocumentReady(startedAt + timeout, () => expectedEventSeen, waitUntil);
736
1190
  }
737
1191
  finally {
738
1192
  unsubscribe();
@@ -742,10 +1196,25 @@ class CdpPoolBrowserPage {
742
1196
  await this.initialize();
743
1197
  return await this.evaluateWithContext(fn);
744
1198
  }
1199
+ async userAgent() {
1200
+ return await this.evaluate("navigator.userAgent");
1201
+ }
745
1202
  async evaluateInFrame(frameId, fn) {
746
1203
  await this.initialize();
747
1204
  const contextId = await this.getFrameExecutionContextId(frameId);
748
- return await this.evaluateWithContext(fn, contextId);
1205
+ try {
1206
+ return await this.evaluateWithContext(fn, contextId);
1207
+ }
1208
+ catch (error) {
1209
+ if (!isMissingExecutionContextError(error)) {
1210
+ throw error;
1211
+ }
1212
+ if (this.frameExecutionContexts.get(frameId) === contextId) {
1213
+ this.frameExecutionContexts.delete(frameId);
1214
+ }
1215
+ const refreshedContextId = await this.getFrameExecutionContextId(frameId);
1216
+ return await this.evaluateWithContext(fn, refreshedContextId);
1217
+ }
749
1218
  }
750
1219
  async waitForSelectorInFrame(frameId, selector, options) {
751
1220
  const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
@@ -845,20 +1314,35 @@ class CdpPoolBrowserPage {
845
1314
  });
846
1315
  return Buffer.from(String(result.data ?? ""), "base64");
847
1316
  }
1317
+ async cookies() {
1318
+ await this.initialize();
1319
+ const result = await this.pageClient.send("Network.getCookies");
1320
+ const cookies = parseCdpCookies(result.cookies);
1321
+ for (const cookie of cookies)
1322
+ registerDiagnosticValue(cookie.value);
1323
+ return cookies;
1324
+ }
848
1325
  async close() {
849
- if (this.closed) {
1326
+ // Only the first caller observes the release outcome; repeat calls stay
1327
+ // idempotent (as they were before the promise was memoized) instead of
1328
+ // replaying a cached rejection, and never re-issue a release against a
1329
+ // target the pool has already disposed.
1330
+ if (this.closePromise) {
1331
+ await this.closePromise.catch(() => undefined);
850
1332
  return;
851
1333
  }
852
- this.closed = true;
853
- try {
854
- await this.release({
855
- ...(this.browserContextId ? { browserContextId: this.browserContextId } : {}),
856
- pageId: this.pageId,
857
- });
858
- }
859
- finally {
860
- await this.pageClient.close();
861
- }
1334
+ this.closePromise = (async () => {
1335
+ try {
1336
+ await this.release({
1337
+ ...(this.browserContextId ? { browserContextId: this.browserContextId } : {}),
1338
+ pageId: this.pageId,
1339
+ });
1340
+ }
1341
+ finally {
1342
+ await this.pageClient.close();
1343
+ }
1344
+ })();
1345
+ await this.closePromise;
862
1346
  }
863
1347
  async withResourcePolicy(policy, run) {
864
1348
  const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
@@ -868,7 +1352,7 @@ class CdpPoolBrowserPage {
868
1352
  const unsubscribe = this.pageClient.on("Fetch.requestPaused", handlePausedRequest);
869
1353
  try {
870
1354
  await this.pageClient.send("Fetch.enable", {
871
- patterns: [{ requestStage: "Request", urlPattern: "*" }],
1355
+ patterns: resourcePolicyFetchPatterns(policy),
872
1356
  });
873
1357
  }
874
1358
  catch (error) {
@@ -883,8 +1367,21 @@ class CdpPoolBrowserPage {
883
1367
  return await run();
884
1368
  }
885
1369
  finally {
886
- unsubscribe();
887
- await this.pageClient.send("Fetch.disable");
1370
+ try {
1371
+ if (this.closePromise) {
1372
+ // Keep interception active until release settles. The release failure
1373
+ // stays observable through close() itself; swallowing it here keeps
1374
+ // this finally from replacing run()'s own outcome, and still avoids a
1375
+ // reconnect or a disable against a disposed target.
1376
+ await this.closePromise.catch(() => undefined);
1377
+ }
1378
+ else {
1379
+ await this.pageClient.send("Fetch.disable");
1380
+ }
1381
+ }
1382
+ finally {
1383
+ unsubscribe();
1384
+ }
888
1385
  }
889
1386
  }
890
1387
  async handleResourcePolicyPausedRequest(params, policy, allowedMethods) {
@@ -893,6 +1390,24 @@ class CdpPoolBrowserPage {
893
1390
  return;
894
1391
  }
895
1392
  try {
1393
+ if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
1394
+ const response = toCdpPausedDocumentResponse(params);
1395
+ if (!response)
1396
+ throw new Error("CDP document response metadata is unavailable");
1397
+ if (!response.hasBody) {
1398
+ await this.pageClient.send("Fetch.continueResponse", {
1399
+ requestId,
1400
+ responseCode: response.responseCode,
1401
+ responseHeaders: [...response.responseHeaders],
1402
+ });
1403
+ return;
1404
+ }
1405
+ const body = await this.pageClient.send("Fetch.getResponseBody", {
1406
+ requestId: response.requestId,
1407
+ });
1408
+ await this.pageClient.send("Fetch.fulfillRequest", toCdpDocumentFulfillParams(response, body, policy.documentContentSecurityPolicy));
1409
+ return;
1410
+ }
896
1411
  const parsed = toCdpResourceRequest(params);
897
1412
  if (!parsed || !allowedMethods.has(parsed.request.method)) {
898
1413
  await this.failCdpResourceRequest(requestId);
@@ -904,6 +1419,11 @@ class CdpPoolBrowserPage {
904
1419
  }
905
1420
  const decision = await resourceRoute.handle(parsed.request);
906
1421
  switch (decision.action) {
1422
+ case "continue":
1423
+ await this.pageClient.send("Fetch.continueRequest", {
1424
+ requestId: parsed.requestId,
1425
+ });
1426
+ return;
907
1427
  case "fulfill":
908
1428
  await this.pageClient.send("Fetch.fulfillRequest", toCdpFulfillParams(parsed.requestId, decision));
909
1429
  return;
@@ -941,8 +1461,45 @@ class CdpPoolBrowserPage {
941
1461
  this.frameExecutionContexts.set(context.frameId, context.id);
942
1462
  }
943
1463
  });
1464
+ this.pageClient.on("Runtime.executionContextDestroyed", (params) => {
1465
+ const destroyedContextId = getCdpDestroyedExecutionContextId(params);
1466
+ if (destroyedContextId === undefined) {
1467
+ return;
1468
+ }
1469
+ for (const [frameId, contextId] of this.frameExecutionContexts) {
1470
+ if (contextId === destroyedContextId) {
1471
+ this.frameExecutionContexts.delete(frameId);
1472
+ }
1473
+ }
1474
+ });
1475
+ this.pageClient.on("Runtime.executionContextsCleared", () => {
1476
+ this.frameExecutionContexts.clear();
1477
+ });
1478
+ const registerResponseCookies = (params) => {
1479
+ if (!isRecord(params))
1480
+ return;
1481
+ const headers = Array.isArray(params.responseHeaders)
1482
+ ? params.responseHeaders
1483
+ : isRecord(params.headers)
1484
+ ? Object.entries(params.headers).map(([name, value]) => ({ name, value }))
1485
+ : [];
1486
+ for (const header of headers) {
1487
+ if (isRecord(header) &&
1488
+ typeof header.name === "string" &&
1489
+ header.name.toLowerCase() === "set-cookie" &&
1490
+ typeof header.value === "string") {
1491
+ registerDiagnosticValue(header.value);
1492
+ for (const value of cookieValuesFromHeaders(header.value))
1493
+ registerDiagnosticValue(value);
1494
+ }
1495
+ }
1496
+ };
1497
+ const scopedCookieRegistration = AsyncResource.bind(registerResponseCookies);
1498
+ this.pageClient.on("Network.responseReceived", scopedCookieRegistration);
1499
+ this.pageClient.on("Network.responseReceivedExtraInfo", scopedCookieRegistration);
944
1500
  await this.pageClient.send("Page.enable");
945
1501
  await this.pageClient.send("Runtime.enable");
1502
+ await this.pageClient.send("Network.enable").catch(() => undefined);
946
1503
  this.initialized = true;
947
1504
  }
948
1505
  async getFrameExecutionContextId(frameId) {
@@ -962,11 +1519,13 @@ class CdpPoolBrowserPage {
962
1519
  this.frameExecutionContexts.set(frameId, contextId);
963
1520
  return contextId;
964
1521
  }
965
- async waitForDocumentReady(deadline, isLoadEventSeen) {
1522
+ async waitForDocumentReady(deadline, isExpectedEventSeen, waitUntil) {
966
1523
  while (Date.now() < deadline) {
967
1524
  const readyState = await this.evaluate("document.readyState");
968
- if (readyState === "complete" || readyState === "interactive") {
969
- if (isLoadEventSeen() || readyState === "complete") {
1525
+ const documentReady = readyState === "complete" ||
1526
+ (waitUntil === "domcontentloaded" && readyState === "interactive");
1527
+ if (documentReady) {
1528
+ if (isExpectedEventSeen() || readyState === "complete") {
970
1529
  return;
971
1530
  }
972
1531
  }
@@ -978,6 +1537,7 @@ class CdpPoolBrowserPage {
978
1537
  class CdpPoolBrowserClient {
979
1538
  allowedHosts;
980
1539
  poolClient;
1540
+ telemetry;
981
1541
  engine = "playwright-stealth";
982
1542
  constructor(options) {
983
1543
  if (!options.cdpUrl) {
@@ -986,17 +1546,30 @@ class CdpPoolBrowserClient {
986
1546
  });
987
1547
  }
988
1548
  this.allowedHosts = [...new Set(options.allowedHosts ?? [])];
1549
+ this.telemetry = options.telemetry;
989
1550
  this.poolClient = new JsonRpcWebSocketClient(options.cdpUrl);
990
1551
  }
991
1552
  async newPage() {
992
1553
  return await this.acquirePage({ isolatedContext: true });
993
1554
  }
994
1555
  async acquirePage(options) {
995
- const acquireResult = parsePoolAcquireResponse(await this.poolClient.send("acquire", {
996
- ...(this.allowedHosts.length > 0 ? { allowedHosts: this.allowedHosts } : {}),
997
- ...(options?.isolatedContext ? { isolationMode: "browserContext" } : {}),
998
- }));
999
- const pageClient = new JsonRpcWebSocketClient(acquireResult.wsEndpoint);
1556
+ let acquireResult;
1557
+ try {
1558
+ acquireResult = parsePoolAcquireResponse(await this.poolClient.send("acquire", {
1559
+ ...(this.allowedHosts.length > 0 ? { allowedHosts: this.allowedHosts } : {}),
1560
+ ...(options?.isolatedContext ? { isolationMode: "browserContext" } : {}),
1561
+ }));
1562
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordPoolAcquire("ok"));
1563
+ }
1564
+ catch (error) {
1565
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordPoolAcquire(poolAcquireOutcome(error)));
1566
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError("BROWSER_CDP_POOL_ERROR"));
1567
+ const code = poolAcquireCode(error);
1568
+ if (code !== undefined && ![-32001, -32002, -32003, -32004, -32005, -32006].includes(code))
1569
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordPoolAcquireUnknownCode?.(code));
1570
+ throw error;
1571
+ }
1572
+ const pageClient = new CdpWebSocketClient(acquireResult.wsEndpoint);
1000
1573
  const page = new CdpPoolBrowserPage(acquireResult.pageId, acquireResult.browserContextId, pageClient, async (request) => {
1001
1574
  await this.poolClient.send("release", request);
1002
1575
  });
@@ -1068,6 +1641,7 @@ export class BrowserClient {
1068
1641
  activePage;
1069
1642
  activePages = new Set();
1070
1643
  _engine;
1644
+ telemetry;
1071
1645
  constructor(options = {}) {
1072
1646
  const resolvedOptions = {
1073
1647
  ...options,
@@ -1075,6 +1649,7 @@ export class BrowserClient {
1075
1649
  };
1076
1650
  const engine = resolvedOptions.engine ?? "playwright-stealth";
1077
1651
  this._engine = engine;
1652
+ this.telemetry = resolvedOptions.telemetry;
1078
1653
  this.cdpUrl = resolvedOptions.cdpUrl;
1079
1654
  if (resolvedOptions.requireCdpPool && !resolvedOptions.cdpUrl) {
1080
1655
  throw new ProviderError("Managed CDP Pool is required for browser providers in production", {
@@ -1099,20 +1674,37 @@ export class BrowserClient {
1099
1674
  return this._engine;
1100
1675
  }
1101
1676
  async newPage() {
1102
- const page = await this.client.newPage();
1103
- return this.activatePage(page);
1677
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordEngine(this._engine));
1678
+ try {
1679
+ const page = await this.client.newPage();
1680
+ return this.activatePage(page);
1681
+ }
1682
+ catch (error) {
1683
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError(browserTelemetryErrorCode(error)));
1684
+ throw error;
1685
+ }
1104
1686
  }
1105
1687
  async rawPage() {
1688
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordEngine(this._engine));
1106
1689
  if (!this.cdpUrl) {
1690
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordPoolAcquire("not_configured"));
1691
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError("BROWSER_RUNTIME_UNSUPPORTED"));
1107
1692
  throw new ProviderError("ctx.browser.rawPage() requires a CDP pool", {
1108
1693
  code: "BROWSER_RUNTIME_UNSUPPORTED",
1109
1694
  fix: "Set APIFUSE__CDP_POOL__URL. The SDK escape hatch is CDP pool-backed only and never launches local Chromium.",
1110
1695
  });
1111
1696
  }
1112
- const page = await this.client.rawPage();
1113
- return this.activatePage(page);
1697
+ try {
1698
+ const page = await this.client.rawPage();
1699
+ return this.activatePage(page);
1700
+ }
1701
+ catch (error) {
1702
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError(browserTelemetryErrorCode(error)));
1703
+ throw error;
1704
+ }
1114
1705
  }
1115
1706
  async withIsolatedContext(handler) {
1707
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordEngine(this._engine));
1116
1708
  const previousActivePage = this.activePage;
1117
1709
  let trackedPage;
1118
1710
  return await this.client.withIsolatedContext(async (page) => {
@@ -1129,24 +1721,35 @@ export class BrowserClient {
1129
1721
  });
1130
1722
  }
1131
1723
  activatePage(page) {
1132
- let closed = false;
1724
+ let closePromise;
1133
1725
  const originalClose = page.close.bind(page);
1134
1726
  const trackedPage = new Proxy(page, {
1135
1727
  get: (target, property, receiver) => {
1136
1728
  if (property === "close") {
1137
1729
  return async () => {
1138
- if (closed)
1730
+ // Repeat calls resolve on the first outcome instead of replaying a
1731
+ // cached rejection, so a failed release cannot make close() — or
1732
+ // BrowserClient.close(), which closes every tracked page — throw
1733
+ // forever.
1734
+ if (closePromise) {
1735
+ await closePromise.catch(() => undefined);
1139
1736
  return;
1140
- closed = true;
1141
- try {
1142
- await originalClose();
1143
1737
  }
1144
- finally {
1145
- this.activePages.delete(trackedPage);
1146
- if (this.activePage === trackedPage) {
1147
- this.activePage = undefined;
1738
+ closePromise = (async () => {
1739
+ // Bookkeeping runs even when the release fails: the page is gone
1740
+ // either way, so leaving it in activePages would leak it and let
1741
+ // solveChallenge reuse a disposed target.
1742
+ try {
1743
+ await originalClose();
1148
1744
  }
1149
- }
1745
+ finally {
1746
+ this.activePages.delete(trackedPage);
1747
+ if (this.activePage === trackedPage) {
1748
+ this.activePage = undefined;
1749
+ }
1750
+ }
1751
+ })();
1752
+ await closePromise;
1150
1753
  };
1151
1754
  }
1152
1755
  const value = Reflect.get(target, property, receiver);
@@ -1158,7 +1761,9 @@ export class BrowserClient {
1158
1761
  return trackedPage;
1159
1762
  }
1160
1763
  async solveChallenge(request) {
1764
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordEngine(this._engine));
1161
1765
  if (request.type !== "recaptcha") {
1766
+ observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError("BROWSER_RUNTIME_UNSUPPORTED"));
1162
1767
  throw new ProviderError(`Unsupported browser challenge: ${request.type}`, {
1163
1768
  code: "BROWSER_RUNTIME_UNSUPPORTED",
1164
1769
  });