@hraness/ghostget 0.17.2

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 (500) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1359 -0
  4. package/bunfig.toml +2 -0
  5. package/dist/apple-photos-client.js +704 -0
  6. package/dist/beeper-client.js +4602 -0
  7. package/dist/client.js +1605 -0
  8. package/dist/index-26yq8q16.js +31 -0
  9. package/dist/index-9t13b4k7.js +508 -0
  10. package/dist/index-axwm9b3v.js +5 -0
  11. package/dist/index-gwk7rbyj.js +30 -0
  12. package/dist/index.js +701 -0
  13. package/dist/messaging.js +1130 -0
  14. package/dist/omni-client.js +1406 -0
  15. package/dist/whatsapp-client.js +355 -0
  16. package/docs/imessage-direct-provider.md +106 -0
  17. package/docs/rental-listings.md +94 -0
  18. package/package.json +600 -0
  19. package/skills/ghostget/SKILL.md +251 -0
  20. package/skills/ghostget/agents/openai.yaml +4 -0
  21. package/skills/ghostget/references/adapter-contract.md +181 -0
  22. package/skills/ghostget/references/apple-photos.md +115 -0
  23. package/skills/ghostget/references/article-drafts.md +235 -0
  24. package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
  25. package/skills/ghostget/references/cross-posting-video.md +139 -0
  26. package/skills/ghostget/references/cross-posting.md +82 -0
  27. package/skills/ghostget/references/derivation.md +390 -0
  28. package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
  29. package/skills/ghostget/references/install.md +66 -0
  30. package/skills/ghostget/references/linkedin-adapter.md +442 -0
  31. package/skills/ghostget/references/messaging.md +168 -0
  32. package/skills/ghostget/references/meta-comet-contract.md +153 -0
  33. package/skills/ghostget/references/platform-patterns.md +122 -0
  34. package/skills/ghostget/references/provider-plugins.md +496 -0
  35. package/skills/ghostget/references/reddit-flair.md +78 -0
  36. package/skills/ghostget/references/safety-and-state.md +214 -0
  37. package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
  38. package/skills/ghostget/references/social-platform-routing.md +66 -0
  39. package/skills/ghostget/references/social-profile-stats.md +195 -0
  40. package/skills/ghostget/references/social-video-platform-routing.md +89 -0
  41. package/skills/ghostget/references/whatsapp-adapter.md +101 -0
  42. package/skills/ghostget/references/x-adapter.md +332 -0
  43. package/skills/ghostget/references/x-ai-disclosure.md +71 -0
  44. package/src/apple-photos-cli.ts +639 -0
  45. package/src/apple-photos-client-types.ts +168 -0
  46. package/src/apple-photos-client.ts +201 -0
  47. package/src/apple-photos-contact-evidence.ts +838 -0
  48. package/src/apple-photos-local-source.ts +1131 -0
  49. package/src/args.ts +2053 -0
  50. package/src/article-draft-document.ts +417 -0
  51. package/src/article-draft-embeds.ts +123 -0
  52. package/src/article-draft-images.ts +214 -0
  53. package/src/assets/adapter-template/wrench-adapter.json +40 -0
  54. package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
  55. package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
  56. package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
  57. package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
  58. package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
  59. package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
  60. package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
  61. package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
  62. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
  63. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
  64. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
  65. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
  66. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
  67. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
  68. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
  69. package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
  70. package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
  71. package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
  72. package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
  73. package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
  74. package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
  75. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
  76. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
  77. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
  78. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
  79. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
  80. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
  81. package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
  82. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
  83. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
  84. package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
  85. package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
  86. package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
  87. package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
  88. package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
  89. package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
  90. package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
  91. package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
  92. package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
  93. package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
  94. package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
  95. package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
  96. package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
  97. package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
  98. package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
  99. package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
  100. package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
  101. package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
  102. package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
  103. package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
  104. package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
  105. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
  106. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
  107. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
  108. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
  109. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
  110. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
  111. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
  112. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
  113. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
  114. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
  115. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
  116. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
  117. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
  118. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
  119. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
  120. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
  121. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
  122. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
  123. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
  124. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
  125. package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
  126. package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
  127. package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
  128. package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
  129. package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
  130. package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
  131. package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
  132. package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
  133. package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
  134. package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
  135. package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
  136. package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
  137. package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
  138. package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
  139. package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
  140. package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
  141. package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
  142. package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
  143. package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
  144. package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
  145. package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
  146. package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
  147. package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
  148. package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
  149. package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
  150. package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
  151. package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
  152. package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
  153. package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
  154. package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
  155. package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
  156. package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
  157. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
  158. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
  159. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
  160. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
  161. package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
  162. package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
  163. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
  164. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
  165. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
  166. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
  167. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
  168. package/src/assets/adapters/x/wrench-adapter.json +749 -0
  169. package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
  170. package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
  171. package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
  172. package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
  173. package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
  174. package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
  175. package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
  176. package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
  177. package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
  178. package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
  179. package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
  180. package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
  181. package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
  182. package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
  183. package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
  184. package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
  185. package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
  186. package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
  187. package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
  188. package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
  189. package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
  190. package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
  191. package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
  192. package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
  193. package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
  194. package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
  195. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  196. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  197. package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
  198. package/src/assets.d.ts +4 -0
  199. package/src/auth.ts +1157 -0
  200. package/src/beeper-client-types.ts +169 -0
  201. package/src/beeper-client.ts +250 -0
  202. package/src/beeper-contact-interactions-cli.ts +180 -0
  203. package/src/beeper-contact-interactions.ts +1612 -0
  204. package/src/beeper-message-bundle-v1.ts +257 -0
  205. package/src/beeper-message-like-me-cli.ts +84 -0
  206. package/src/beeper-message-like-me-export.ts +2274 -0
  207. package/src/beeper-message-like-me-recovery.ts +931 -0
  208. package/src/beeper-message-like-me-source.ts +4034 -0
  209. package/src/browser-admission.ts +928 -0
  210. package/src/browser-snapshots.ts +260 -0
  211. package/src/browser.ts +3873 -0
  212. package/src/canonical-json.ts +60 -0
  213. package/src/catalog-cli.ts +638 -0
  214. package/src/cli.ts +208 -0
  215. package/src/client-types.ts +301 -0
  216. package/src/client.ts +2726 -0
  217. package/src/confirmed-write-failure.ts +44 -0
  218. package/src/confirmed-write-model.ts +72 -0
  219. package/src/confirmed-write-platform.ts +1055 -0
  220. package/src/confirmed-write-program.ts +224 -0
  221. package/src/confirmed-write-runtime.ts +16 -0
  222. package/src/cursor-token.ts +228 -0
  223. package/src/derivation-dnr-readiness.ts +367 -0
  224. package/src/derivation-file-chooser.ts +384 -0
  225. package/src/derivation-network-boundary.ts +835 -0
  226. package/src/derivation-network-guard.ts +782 -0
  227. package/src/derivation-network-proxy-helper.ts +440 -0
  228. package/src/derivation-network-proxy.ts +385 -0
  229. package/src/derive-command-helper.ts +693 -0
  230. package/src/derive-fixtures.ts +343 -0
  231. package/src/derive-review.ts +980 -0
  232. package/src/derive.ts +4754 -0
  233. package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
  234. package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
  235. package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
  236. package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
  237. package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
  238. package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
  239. package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
  240. package/src/ghostget.ts +3672 -0
  241. package/src/gmail-capture.ts +1244 -0
  242. package/src/har-internal.ts +1242 -0
  243. package/src/har.ts +743 -0
  244. package/src/index.ts +218 -0
  245. package/src/invocation-read-platform.ts +55 -0
  246. package/src/invocation-read-program.ts +93 -0
  247. package/src/invocation-read-runtime.ts +11 -0
  248. package/src/linked-device-lifecycle-admission.ts +450 -0
  249. package/src/linked-device-lifecycle-journal.ts +1560 -0
  250. package/src/linked-device-lifecycle-runtime.ts +1602 -0
  251. package/src/local-cli-admission.ts +135 -0
  252. package/src/local-cli-contracts.ts +271 -0
  253. package/src/local-cli-execution.ts +98 -0
  254. package/src/local-cli-surface-contract.ts +1510 -0
  255. package/src/local-cli-tool-identity.ts +336 -0
  256. package/src/media/archive.ts +2325 -0
  257. package/src/media/args.ts +303 -0
  258. package/src/media/cli.ts +287 -0
  259. package/src/media/doctor.ts +475 -0
  260. package/src/media/ffmpeg.ts +1330 -0
  261. package/src/media/http-capture.ts +711 -0
  262. package/src/media/http-probe.ts +593 -0
  263. package/src/media/http.ts +387 -0
  264. package/src/media/index.ts +10 -0
  265. package/src/media/local-transcription.ts +613 -0
  266. package/src/media/lock.ts +321 -0
  267. package/src/media/manifest.ts +1712 -0
  268. package/src/media/metadata.ts +906 -0
  269. package/src/media/process.ts +723 -0
  270. package/src/media/revision.ts +166 -0
  271. package/src/media/runtime-closure.ts +1063 -0
  272. package/src/media/source-router.ts +187 -0
  273. package/src/media/transcriber-config.ts +1122 -0
  274. package/src/media/transcript-persistence-model.ts +15 -0
  275. package/src/media/transcript-persistence-platform.ts +89 -0
  276. package/src/media/transcript-persistence-program.ts +39 -0
  277. package/src/media/transcript-persistence-runtime.ts +24 -0
  278. package/src/media/transcript.ts +616 -0
  279. package/src/media/utf8-order.ts +30 -0
  280. package/src/media/whisper-cpp.ts +464 -0
  281. package/src/media/whisper-language.ts +58 -0
  282. package/src/media/yt-dlp.ts +439 -0
  283. package/src/message-like-me-agentic-messaging.ts +882 -0
  284. package/src/messaging-action-store.ts +793 -0
  285. package/src/messaging-runtime.ts +2136 -0
  286. package/src/messaging-store.ts +539 -0
  287. package/src/messaging-types.ts +1663 -0
  288. package/src/messaging.ts +334 -0
  289. package/src/model.ts +2671 -0
  290. package/src/oauth-google.ts +888 -0
  291. package/src/omni-client-types.ts +306 -0
  292. package/src/omni-client.ts +2225 -0
  293. package/src/omni-limits.ts +34 -0
  294. package/src/omni-model.ts +1614 -0
  295. package/src/omni-request.ts +379 -0
  296. package/src/omni-runtime.ts +1632 -0
  297. package/src/operation-deadline.ts +189 -0
  298. package/src/path-helper.ts +1656 -0
  299. package/src/pinned-https.ts +194 -0
  300. package/src/plan-assets.ts +832 -0
  301. package/src/platform-catalog.ts +1832 -0
  302. package/src/plugins/beeper-linked-device/plugin.ts +310 -0
  303. package/src/plugins/bluesky-web/plugin.ts +178 -0
  304. package/src/plugins/clasificados-web/plugin.ts +66 -0
  305. package/src/plugins/github-web/plugin.ts +80 -0
  306. package/src/plugins/gmail-official/plugin.ts +110 -0
  307. package/src/plugins/hacker-news-web/plugin.ts +52 -0
  308. package/src/plugins/imessage-direct/plugin.ts +195 -0
  309. package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
  310. package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
  311. package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
  312. package/src/plugins/linkedin-official/plugin.ts +84 -0
  313. package/src/plugins/linkedin-web/plugin.ts +412 -0
  314. package/src/plugins/meta-web/plugin.ts +317 -0
  315. package/src/plugins/reddit-web/flair.ts +492 -0
  316. package/src/plugins/reddit-web/plugin.ts +477 -0
  317. package/src/plugins/substack-web/plugin.ts +124 -0
  318. package/src/plugins/tiktok-web/plugin.ts +67 -0
  319. package/src/plugins/twitch-web/plugin.ts +54 -0
  320. package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
  321. package/src/plugins/x-official/plugin.ts +64 -0
  322. package/src/plugins/x-web/plugin.ts +488 -0
  323. package/src/plugins/youtube-web/plugin.ts +101 -0
  324. package/src/portable-run-recovery.ts +466 -0
  325. package/src/process-identity.ts +501 -0
  326. package/src/profile-clone-helper.ts +104 -0
  327. package/src/provider-catalog-views.ts +51 -0
  328. package/src/provider-context.ts +69 -0
  329. package/src/provider-contract-definitions-x.ts +45 -0
  330. package/src/provider-contract-definitions.ts +948 -0
  331. package/src/provider-contract-input-gmail.ts +176 -0
  332. package/src/provider-contract-input-linkedin.ts +77 -0
  333. package/src/provider-contract-input-x.ts +304 -0
  334. package/src/provider-contract-planning.ts +23 -0
  335. package/src/provider-contract-semantic-identity.ts +54 -0
  336. package/src/provider-contracts.ts +251 -0
  337. package/src/provider-http.ts +723 -0
  338. package/src/provider-plugin-auth.ts +36 -0
  339. package/src/provider-plugin-builtins.ts +229 -0
  340. package/src/provider-plugin-cleanup-barrier.ts +229 -0
  341. package/src/provider-plugin-cleanup-execution.ts +79 -0
  342. package/src/provider-plugin-cleanup-resource.ts +470 -0
  343. package/src/provider-plugin-contract-identity.ts +707 -0
  344. package/src/provider-plugin-host.ts +1972 -0
  345. package/src/provider-plugin-identifiers.ts +60 -0
  346. package/src/provider-plugin-invocation-lease.ts +889 -0
  347. package/src/provider-plugin-lifecycle-kernel.ts +566 -0
  348. package/src/provider-plugin-lifecycle.ts +1455 -0
  349. package/src/provider-plugin-module-analysis.ts +336 -0
  350. package/src/provider-plugin-package.ts +2530 -0
  351. package/src/provider-plugin-portable-authority.ts +106 -0
  352. package/src/provider-plugin-portable-catalog.ts +94 -0
  353. package/src/provider-plugin-portable-identity.ts +315 -0
  354. package/src/provider-plugin-portable-runtime.ts +2057 -0
  355. package/src/provider-plugin-protocol.ts +1625 -0
  356. package/src/provider-plugin-registry.ts +3342 -0
  357. package/src/provider-plugin-store.ts +2155 -0
  358. package/src/provider-plugin-test-fixture.ts +5 -0
  359. package/src/provider-plugin.ts +4907 -0
  360. package/src/provider-plugins.generated.ts +43 -0
  361. package/src/provider-plugins.ts +8 -0
  362. package/src/provider-subject.ts +41 -0
  363. package/src/provider.ts +315 -0
  364. package/src/providers/beeper-local-runtime.ts +5426 -0
  365. package/src/providers/beeper-local.ts +2739 -0
  366. package/src/providers/beeper-messaging.ts +462 -0
  367. package/src/providers/beeper-omni.ts +1159 -0
  368. package/src/providers/bluesky-web-runtime.ts +2895 -0
  369. package/src/providers/bluesky-web.ts +1614 -0
  370. package/src/providers/clasificados-web-runtime.ts +255 -0
  371. package/src/providers/clasificados-web.ts +503 -0
  372. package/src/providers/contact-projection.ts +301 -0
  373. package/src/providers/github-read-model.ts +94 -0
  374. package/src/providers/github-read-platform.ts +165 -0
  375. package/src/providers/github-read-program.ts +97 -0
  376. package/src/providers/github-web-runtime.ts +320 -0
  377. package/src/providers/github-web.ts +403 -0
  378. package/src/providers/gmail-api.ts +2834 -0
  379. package/src/providers/gmail-omni.ts +830 -0
  380. package/src/providers/gmail.ts +1130 -0
  381. package/src/providers/hacker-news-web-runtime.ts +220 -0
  382. package/src/providers/hacker-news-web.ts +809 -0
  383. package/src/providers/imessage-direct-install.ts +354 -0
  384. package/src/providers/imessage-direct-messaging.ts +532 -0
  385. package/src/providers/imessage-direct-omni.ts +400 -0
  386. package/src/providers/imessage-direct-runtime.ts +1498 -0
  387. package/src/providers/imessage-direct.ts +351 -0
  388. package/src/providers/instagram-video-foundations.ts +913 -0
  389. package/src/providers/instagram-web-profile-browser.ts +600 -0
  390. package/src/providers/iso-bmff.ts +338 -0
  391. package/src/providers/linkedin-company-platform.ts +42 -0
  392. package/src/providers/linkedin-company-program.ts +88 -0
  393. package/src/providers/linkedin-contact-failure.ts +84 -0
  394. package/src/providers/linkedin-contact-platform.ts +45 -0
  395. package/src/providers/linkedin-contact-program.ts +91 -0
  396. package/src/providers/linkedin-profile-activity-failure.ts +76 -0
  397. package/src/providers/linkedin-profile-activity-platform.ts +41 -0
  398. package/src/providers/linkedin-profile-activity-program.ts +67 -0
  399. package/src/providers/linkedin-read-failure.ts +52 -0
  400. package/src/providers/linkedin-self-platform.ts +53 -0
  401. package/src/providers/linkedin-self-program.ts +122 -0
  402. package/src/providers/linkedin-web-article-browser.ts +794 -0
  403. package/src/providers/linkedin-web-bootstrap.ts +146 -0
  404. package/src/providers/linkedin-web-contact.ts +830 -0
  405. package/src/providers/linkedin-web-feed-browser.ts +832 -0
  406. package/src/providers/linkedin-web-feed.ts +864 -0
  407. package/src/providers/linkedin-web-post-browser.ts +653 -0
  408. package/src/providers/linkedin-web-profile-browser.ts +683 -0
  409. package/src/providers/linkedin-web-runtime.ts +2388 -0
  410. package/src/providers/linkedin-web.ts +3305 -0
  411. package/src/providers/linkedin.ts +1885 -0
  412. package/src/providers/meta-bootstrap.ts +1043 -0
  413. package/src/providers/meta-facebook-group.ts +690 -0
  414. package/src/providers/meta-marketplace-relay.ts +747 -0
  415. package/src/providers/meta-omni.ts +373 -0
  416. package/src/providers/meta-relay-bundle.ts +1049 -0
  417. package/src/providers/meta-web-descriptors.ts +1740 -0
  418. package/src/providers/meta-web-runtime.ts +3388 -0
  419. package/src/providers/meta-web.ts +2931 -0
  420. package/src/providers/read-failure.ts +139 -0
  421. package/src/providers/reddit-omni.ts +462 -0
  422. package/src/providers/reddit-web-runtime.ts +1929 -0
  423. package/src/providers/reddit-web.ts +1611 -0
  424. package/src/providers/rental-listings.ts +460 -0
  425. package/src/providers/substack-omni.ts +406 -0
  426. package/src/providers/substack-video-mp4.ts +33 -0
  427. package/src/providers/substack-web-runtime.ts +2812 -0
  428. package/src/providers/substack-web.ts +1502 -0
  429. package/src/providers/tiktok-video-mp4.ts +33 -0
  430. package/src/providers/tiktok-web-runtime.ts +770 -0
  431. package/src/providers/tiktok-web.ts +2786 -0
  432. package/src/providers/twitch-web-runtime.ts +226 -0
  433. package/src/providers/twitch-web.ts +274 -0
  434. package/src/providers/whatsapp-account-identity.ts +55 -0
  435. package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
  436. package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
  437. package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
  438. package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
  439. package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
  440. package/src/providers/whatsapp-messaging.ts +132 -0
  441. package/src/providers/whatsapp-omni.ts +586 -0
  442. package/src/providers/whatsapp-web-runtime.ts +3108 -0
  443. package/src/providers/whatsapp-web.ts +1037 -0
  444. package/src/providers/x-image-provenance.ts +523 -0
  445. package/src/providers/x-made-with-ai.ts +106 -0
  446. package/src/providers/x-transaction-id.ts +413 -0
  447. package/src/providers/x-web-runtime.ts +3485 -0
  448. package/src/providers/x-web.ts +2332 -0
  449. package/src/providers/x.ts +1889 -0
  450. package/src/providers/youtube-web-runtime.ts +1400 -0
  451. package/src/providers/youtube-web.ts +1221 -0
  452. package/src/read-admission-runtime.ts +91 -0
  453. package/src/read-client.ts +521 -0
  454. package/src/read-effect-platform.ts +7 -0
  455. package/src/read-effect-runtime.ts +14 -0
  456. package/src/read-effect.ts +52 -0
  457. package/src/read-projection-admission.ts +866 -0
  458. package/src/read-projections.ts +3151 -0
  459. package/src/recovery.ts +1492 -0
  460. package/src/reviewed-template.ts +146 -0
  461. package/src/run-journal.ts +1272 -0
  462. package/src/runtime.ts +5116 -0
  463. package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
  464. package/src/scripts/install-whatsapp-protocol.sh +232 -0
  465. package/src/scripts/recover-install-stages.ts +1491 -0
  466. package/src/scripts/resolve-state-home.ts +30 -0
  467. package/src/scripts/scaffold-web-provider.ts +973 -0
  468. package/src/scripts/sync-bundled-adapters.ts +695 -0
  469. package/src/session-secrets.ts +1183 -0
  470. package/src/state-helper.bunfig.toml +2 -0
  471. package/src/state-helper.ts +2914 -0
  472. package/src/storage.ts +3842 -0
  473. package/src/transport-policy.ts +2 -0
  474. package/src/usage.ts +182 -0
  475. package/src/version.ts +2 -0
  476. package/src/web-session-authentication-policy.ts +136 -0
  477. package/src/web-session-cleanup-admission.ts +2344 -0
  478. package/src/web-session-cleanup-join.ts +99 -0
  479. package/src/web-session-client.ts +996 -0
  480. package/src/web-session-contract-definitions.ts +820 -0
  481. package/src/web-session-contract-planning.ts +42 -0
  482. package/src/web-session-contracts.ts +228 -0
  483. package/src/web-session-cookies.ts +500 -0
  484. package/src/web-session-execution.ts +385 -0
  485. package/src/web-session-http.ts +373 -0
  486. package/src/web-session-read-errors.ts +19 -0
  487. package/src/web-session-read-runtime.ts +124 -0
  488. package/src/web-session-recovery.ts +818 -0
  489. package/src/web-session-sites.ts +58 -0
  490. package/src/web-session-template.ts +994 -0
  491. package/src/web-session.ts +93 -0
  492. package/src/whatsapp-client-binding.ts +21 -0
  493. package/src/whatsapp-client-types.ts +75 -0
  494. package/src/whatsapp-client.ts +395 -0
  495. package/src/whatsapp-export-coordinate.ts +17 -0
  496. package/src/whatsapp-message-bundle-v2.ts +195 -0
  497. package/src/whatsapp-message-like-me-cli.ts +195 -0
  498. package/src/whatsapp-message-like-me-export.ts +1072 -0
  499. package/src/whatsapp-message-like-me-source.ts +1012 -0
  500. package/tsconfig.json +67 -0
@@ -0,0 +1,832 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import type { GhostgetAuth } from "../auth";
4
+ import {
5
+ PreservedBrowserArtifactsError,
6
+ browserResultData,
7
+ createBrowserSession,
8
+ runCommand,
9
+ type BrowserSession,
10
+ type CommandResult,
11
+ type CreateBrowserSessionOptions,
12
+ } from "../browser";
13
+ import type { GhostgetManifest } from "../model";
14
+ import type {
15
+ WebSessionCleanupResourcePublisher,
16
+ WebSessionOperationDeadline,
17
+ } from "../web-session-execution";
18
+ import {
19
+ LINKEDIN_GRAPHQL_PATH,
20
+ LINKEDIN_PROFILE_ACTIVITY_QUERY_PREFIX,
21
+ assertLinkedInProfileActivityRequest,
22
+ linkedInProfileActivityPageUrl,
23
+ linkedInProfileActivityTargetFromVanity,
24
+ resolveLinkedInProfileActivityBinding,
25
+ type LinkedInProfileActivityBinding,
26
+ } from "./linkedin-web-feed";
27
+ import { jsonScriptLiteral } from "../canonical-json";
28
+
29
+ const LINKEDIN_ORIGIN = "https://www.linkedin.com";
30
+ const LINKEDIN_FEED_URL = `${LINKEDIN_ORIGIN}/feed/`;
31
+ const LINKEDIN_PRE_COOKIE_REALM_URL = `${LINKEDIN_ORIGIN}/robots.txt`;
32
+ const LINKEDIN_INITIAL_ROOT_BATCH = JSON.stringify([["open", LINKEDIN_ORIGIN]]);
33
+ const LINKEDIN_INITIAL_BLANK_BATCH = JSON.stringify([["open", "about:blank"]]);
34
+ const LINKEDIN_INITIAL_REALM_BATCH = JSON.stringify([["open", LINKEDIN_PRE_COOKIE_REALM_URL]]);
35
+ const MAX_IDENTITY_BYTES = 2 * 1024 * 1024;
36
+ const MAX_FEED_PAGE_BYTES = 8 * 1024 * 1024;
37
+ const BROWSER_ENVELOPE_BYTES = 64 * 1024;
38
+ const MAX_NETWORK_REQUESTS = 10_000;
39
+ const MAX_REQUEST_URL_CHARACTERS = 64 * 1_024;
40
+
41
+ const feedBrowserManifest: GhostgetManifest = Object.freeze({
42
+ schemaVersion: 4,
43
+ id: "linkedin-profile-activity-runtime",
44
+ version: "1.0.0",
45
+ displayName: "LinkedIn profile-activity runtime",
46
+ surfaceId: "linkedin",
47
+ origins: Object.freeze([LINKEDIN_ORIGIN]),
48
+ browserDomains: Object.freeze(["www.linkedin.com", "static.licdn.com"]),
49
+ operations: Object.freeze({}),
50
+ });
51
+
52
+ export type LinkedInFeedBrowserTransport = {
53
+ readonly currentIdentityResponse: () => Promise<unknown>;
54
+ readonly resolveProfileActivityBinding: (vanity: string) => Promise<LinkedInProfileActivityBinding>;
55
+ readonly readProfileActivityPage: (input: {
56
+ readonly count: number;
57
+ readonly start: number;
58
+ }) => Promise<unknown>;
59
+ readonly close: () => Promise<void>;
60
+ };
61
+
62
+ export type LinkedInFeedBrowserDependencies = {
63
+ readonly createBrowserSession: typeof createBrowserSession;
64
+ readonly runCommand: typeof runCommand;
65
+ readonly settleContext: () => Promise<void>;
66
+ };
67
+
68
+ type BrowserReadBinding = {
69
+ readonly documentUrl: string | null;
70
+ readonly kind: "json";
71
+ readonly maxBytes: number;
72
+ readonly path: string;
73
+ readonly referrer: string;
74
+ };
75
+
76
+ type BrowserReadState = "ready" | "identity" | "bound";
77
+
78
+ type ObservedBrowserRequest = Readonly<Record<string, unknown>> & {
79
+ readonly requestId: string;
80
+ };
81
+
82
+ type BoundProfileActivityPage = {
83
+ readonly binding: LinkedInProfileActivityBinding;
84
+ readonly targetUrl: string;
85
+ readonly vanity: string;
86
+ };
87
+
88
+ const LINKEDIN_RESPONSE_MEDIA_TYPE =
89
+ /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/u;
90
+
91
+ export type LinkedInFeedBrowserFailureCategory =
92
+ | "authwall"
93
+ | "body-envelope"
94
+ | "bootstrap"
95
+ | "browser-envelope"
96
+ | "browser-command"
97
+ | "execution-context"
98
+ | "identity-json"
99
+ | "output-bound"
100
+ | "provider-fetch"
101
+ | "response-envelope"
102
+ | "response-rejected"
103
+ | "session-cookie"
104
+ | "startup";
105
+
106
+ export class LinkedInFeedBrowserFailure extends Error {
107
+ readonly category: LinkedInFeedBrowserFailureCategory;
108
+
109
+ constructor(category: LinkedInFeedBrowserFailureCategory, message: string) {
110
+ super(message);
111
+ this.name = "LinkedInFeedBrowserFailure";
112
+ this.category = category;
113
+ }
114
+ }
115
+
116
+ export class LinkedInFeedBrowserResponseRejectedError extends LinkedInFeedBrowserFailure {
117
+ readonly status: number;
118
+ readonly contentType: string;
119
+
120
+ constructor(status: number, contentType: string) {
121
+ super(
122
+ "response-rejected",
123
+ "LinkedIn profile-activity browser request returned a reviewed rejection",
124
+ );
125
+ this.name = "LinkedInFeedBrowserResponseRejectedError";
126
+ if (
127
+ !Number.isSafeInteger(status)
128
+ || status < 100
129
+ || status > 599
130
+ || contentType.length > 128
131
+ || (contentType !== "" && !LINKEDIN_RESPONSE_MEDIA_TYPE.test(contentType))
132
+ ) throw new Error("LinkedIn profile-activity browser returned a malformed response category");
133
+ this.status = status;
134
+ this.contentType = contentType === "" ? "missing" : contentType;
135
+ }
136
+ }
137
+
138
+ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
139
+ return typeof value === "object" && value !== null && !Array.isArray(value);
140
+ }
141
+
142
+ function exactKeys(
143
+ value: Readonly<Record<string, unknown>>,
144
+ expected: readonly string[],
145
+ label: string,
146
+ ): void {
147
+ if (Object.keys(value).sort().join(",") !== [...expected].sort().join(",")) {
148
+ throw new Error(`${label} returned an unexpected result shape`);
149
+ }
150
+ }
151
+
152
+ function browserReadEvaluationSource(binding: BrowserReadBinding): string {
153
+ const bound = jsonScriptLiteral(binding);
154
+ return `(async()=>{const input=${bound};if(location.origin!=="${LINKEDIN_ORIGIN}")throw new Error("unexpected LinkedIn origin");if(input.kind!=="json"||!Number.isSafeInteger(input.maxBytes)||input.maxBytes<1||input.maxBytes>${MAX_FEED_PAGE_BYTES}||(input.documentUrl!==null&&typeof input.documentUrl!=="string"))throw new Error("invalid LinkedIn profile-activity browser request binding");if(input.documentUrl!==null&&location.href!==input.documentUrl){if(location.origin==="${LINKEDIN_ORIGIN}"&&/^\/(?:authwall|checkpoint|login|uas\/login(?:-submit)?)(?:\/|$)/u.test(location.pathname))throw new Error("LinkedIn profile-activity browser reached its signed-out authwall");throw new Error("LinkedIn profile-activity browser left its bound document")}const expected=new URL(input.path,"${LINKEDIN_ORIGIN}");if(expected.origin!=="${LINKEDIN_ORIGIN}"||expected.username!==""||expected.password!==""||expected.hash!==""||expected.href!=="${LINKEDIN_ORIGIN}"+input.path)throw new Error("invalid LinkedIn profile-activity browser path binding");if(expected.pathname!=="/voyager/api/me"&&expected.pathname!=="${LINKEDIN_GRAPHQL_PATH}")throw new Error("invalid LinkedIn profile-activity browser path binding");const headers={accept:"application/vnd.linkedin.normalized+json+2.1","x-li-lang":"en_US","x-requested-with":"XMLHttpRequest","x-restli-protocol-version":"2.0.0"};const raw=document.cookie.split("; ").find((part)=>part.startsWith("JSESSIONID="));if(typeof raw!=="string")throw new Error("missing LinkedIn browser CSRF cookie");const csrf=decodeURIComponent(raw.slice("JSESSIONID=".length)).replace(/^\"|\"$/g,"");if(!/^ajax:[A-Za-z0-9_-]{1,512}$/.test(csrf))throw new Error("invalid LinkedIn browser CSRF cookie");headers["csrf-token"]=csrf;const response=await fetch(input.path,{credentials:"include",headers,method:"GET",redirect:"manual",referrer:input.referrer});if(response.type==="opaqueredirect"){response.body?.cancel();return{authWall:true,bodyBase64:null,bodyBytes:0,bodySha256:null,contentType:"",status:0}}const responseUrl=new URL(response.url);if(responseUrl.origin!=="${LINKEDIN_ORIGIN}"||responseUrl.username!==""||responseUrl.password!==""||responseUrl.hash!==""||responseUrl.href!==expected.href)throw new Error("LinkedIn profile-activity browser response escaped its exact route");if(response.status>=300&&response.status<=399){response.body?.cancel();return{authWall:true,bodyBase64:null,bodyBytes:0,bodySha256:null,contentType:"",status:response.status}}const contentType=(response.headers.get("content-type")||"").split(";",1)[0].trim().toLowerCase();if(response.status!==200||(contentType!=="application/vnd.linkedin.normalized+json+2.1"&&contentType!=="application/json")){response.body?.cancel();return{authWall:false,bodyBase64:null,bodyBytes:0,bodySha256:null,contentType,status:response.status}}if(response.body===null)throw new Error("LinkedIn profile-activity browser response omitted its body");const reader=response.body.getReader();const chunks=[];let bytes=0;while(true){const part=await reader.read();if(part.done)break;bytes+=part.value.byteLength;if(bytes>input.maxBytes){await reader.cancel();throw new Error("LinkedIn profile-activity browser response exceeded its reviewed byte bound")}chunks.push(part.value)}const body=new Uint8Array(bytes);let cursor=0;for(const chunk of chunks){body.set(chunk,cursor);cursor+=chunk.byteLength}const digest=Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256",body)),(value)=>value.toString(16).padStart(2,"0")).join("");let binary="";for(let offset=0;offset<body.length;offset+=32768)binary+=String.fromCharCode(...body.subarray(offset,Math.min(offset+32768,body.length)));return{authWall:false,bodyBase64:btoa(binary),bodyBytes:body.byteLength,bodySha256:digest,contentType,status:response.status}})()`;
155
+ }
156
+
157
+ function encodedBodyBound(bytes: number): number {
158
+ return Math.ceil(bytes / 3) * 4;
159
+ }
160
+
161
+ function decodedBody(
162
+ result: Readonly<Record<string, unknown>>,
163
+ maximumBytes: number,
164
+ ): string {
165
+ if (
166
+ typeof result.bodyBase64 !== "string"
167
+ || result.bodyBase64.length > encodedBodyBound(maximumBytes)
168
+ || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(
169
+ result.bodyBase64,
170
+ )
171
+ || !Number.isSafeInteger(result.bodyBytes)
172
+ || (result.bodyBytes as number) < 0
173
+ || (result.bodyBytes as number) > maximumBytes
174
+ || typeof result.bodySha256 !== "string"
175
+ || !/^[a-f0-9]{64}$/u.test(result.bodySha256)
176
+ ) throw new LinkedInFeedBrowserFailure(
177
+ "body-envelope",
178
+ "LinkedIn profile-activity browser body envelope changed shape",
179
+ );
180
+ const bytes = Buffer.from(result.bodyBase64, "base64");
181
+ if (
182
+ bytes.byteLength !== result.bodyBytes
183
+ || bytes.toString("base64") !== result.bodyBase64
184
+ || createHash("sha256").update(bytes).digest("hex") !== result.bodySha256
185
+ ) throw new LinkedInFeedBrowserFailure(
186
+ "body-envelope",
187
+ "LinkedIn profile-activity browser body envelope failed integrity verification",
188
+ );
189
+ try {
190
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
191
+ } catch {
192
+ throw new LinkedInFeedBrowserFailure(
193
+ "body-envelope",
194
+ "LinkedIn profile-activity browser body was not valid UTF-8",
195
+ );
196
+ }
197
+ }
198
+
199
+ function browserEvaluationResult(
200
+ value: Readonly<Record<string, unknown>>,
201
+ ): Readonly<Record<string, unknown>> {
202
+ const data = browserResultData(value as Record<string, unknown>);
203
+ if (!isRecord(data) || typeof data.origin !== "string" || !isRecord(data.result)) {
204
+ throw new LinkedInFeedBrowserFailure(
205
+ "response-envelope",
206
+ "LinkedIn profile-activity browser returned a malformed evaluation envelope",
207
+ );
208
+ }
209
+ let origin: URL;
210
+ try {
211
+ origin = new URL(data.origin);
212
+ } catch {
213
+ throw new LinkedInFeedBrowserFailure(
214
+ "response-envelope",
215
+ "LinkedIn profile-activity browser returned a malformed evaluation envelope",
216
+ );
217
+ }
218
+ if (
219
+ origin.origin !== LINKEDIN_ORIGIN
220
+ || origin.username !== ""
221
+ || origin.password !== ""
222
+ ) throw new LinkedInFeedBrowserFailure(
223
+ "response-envelope",
224
+ "LinkedIn profile-activity browser returned a malformed evaluation envelope",
225
+ );
226
+ return data.result;
227
+ }
228
+
229
+ function hasNoDefaultExecutionContext(error: unknown): boolean {
230
+ let current: unknown = error;
231
+ for (let depth = 0; depth < 8 && current !== undefined; depth += 1) {
232
+ if (
233
+ current instanceof Error
234
+ && /(?:cannot find|no) default execution context/iu.test(current.message)
235
+ ) return true;
236
+ current = current instanceof Error ? current.cause : undefined;
237
+ }
238
+ return false;
239
+ }
240
+
241
+ function hasUnexpectedLinkedInOrigin(error: unknown): boolean {
242
+ let current: unknown = error;
243
+ for (let depth = 0; depth < 8 && current !== undefined; depth += 1) {
244
+ if (
245
+ current instanceof Error
246
+ && /(?:^|: )unexpected LinkedIn origin(?:$|[\r\n])/u.test(current.message)
247
+ ) return true;
248
+ current = current instanceof Error ? current.cause : undefined;
249
+ }
250
+ return false;
251
+ }
252
+
253
+ function classifiedBrowserCommandFailure(error: unknown): LinkedInFeedBrowserFailure {
254
+ const message = error instanceof Error && error.message.length <= 1_500
255
+ ? error.message
256
+ : "";
257
+ if (/(?:^|: )(?:missing|invalid) LinkedIn browser CSRF cookie$/u.test(message)) {
258
+ return new LinkedInFeedBrowserFailure(
259
+ "session-cookie",
260
+ "LinkedIn profile-activity browser could not establish its reviewed CSRF cookie",
261
+ );
262
+ }
263
+ if (message.includes("Failed to fetch")) {
264
+ return new LinkedInFeedBrowserFailure(
265
+ "provider-fetch",
266
+ "LinkedIn profile-activity browser could not complete its first-party fetch",
267
+ );
268
+ }
269
+ if (message.endsWith("LinkedIn profile-activity browser response escaped its exact route")) {
270
+ return new LinkedInFeedBrowserFailure(
271
+ "response-envelope",
272
+ "LinkedIn profile-activity browser response escaped its exact route",
273
+ );
274
+ }
275
+ if (message.endsWith("LinkedIn profile-activity browser left its bound document")) {
276
+ return new LinkedInFeedBrowserFailure(
277
+ "response-envelope",
278
+ "LinkedIn profile-activity browser left its exact target page",
279
+ );
280
+ }
281
+ if (message.endsWith("LinkedIn profile-activity browser reached its signed-out authwall")) {
282
+ return new LinkedInFeedBrowserFailure(
283
+ "authwall",
284
+ "LinkedIn profile-activity browser reached the signed-out authwall",
285
+ );
286
+ }
287
+ if (
288
+ message.includes("process output exceeded")
289
+ || message.includes("response exceeded its reviewed byte bound")
290
+ ) {
291
+ return new LinkedInFeedBrowserFailure(
292
+ "output-bound",
293
+ "LinkedIn profile-activity browser exceeded a reviewed output bound",
294
+ );
295
+ }
296
+ if (
297
+ message.includes("malformed batch")
298
+ || message.includes("malformed batch entry")
299
+ || message.includes("did not return JSON")
300
+ || message.includes("command omitted its result")
301
+ ) {
302
+ return new LinkedInFeedBrowserFailure(
303
+ "browser-envelope",
304
+ "LinkedIn profile-activity browser command returned a malformed envelope",
305
+ );
306
+ }
307
+ return new LinkedInFeedBrowserFailure(
308
+ "browser-command",
309
+ "LinkedIn profile-activity browser command failed before a reviewed response",
310
+ );
311
+ }
312
+
313
+ function contextSettlementRejected(result: CommandResult): boolean {
314
+ return result.exitCode !== 0
315
+ && /Failed to install browser network controls:[^\r\n]{0,256}Cannot find default execution context/u.test(
316
+ `${result.stderr}\n${result.stdout}`,
317
+ );
318
+ }
319
+
320
+ function commandWasAborted(options: Parameters<typeof runCommand>[1]): boolean {
321
+ return options.signal?.aborted === true;
322
+ }
323
+
324
+ function linkedInFeedBrowserCommandRunner(
325
+ execute: typeof runCommand,
326
+ settleContext: () => Promise<void>,
327
+ authKind: GhostgetAuth["kind"],
328
+ ): typeof runCommand {
329
+ let initialBatchPending = true;
330
+ return async (command, options) => {
331
+ const rewroteInitialRoot = initialBatchPending
332
+ && options.stdin === LINKEDIN_INITIAL_ROOT_BATCH;
333
+ const rewroteInitialBlank = initialBatchPending
334
+ && authKind === "browser-profile"
335
+ && options.stdin === LINKEDIN_INITIAL_BLANK_BATCH;
336
+ const executionOptions = rewroteInitialRoot || rewroteInitialBlank
337
+ ? { ...options, stdin: LINKEDIN_INITIAL_REALM_BATCH }
338
+ : options;
339
+ initialBatchPending = false;
340
+ const first = await execute(command, executionOptions);
341
+ if (
342
+ !rewroteInitialRoot
343
+ || authKind === "browser-profile"
344
+ || !contextSettlementRejected(first)
345
+ || commandWasAborted(executionOptions)
346
+ ) return first;
347
+ await settleContext();
348
+ if (commandWasAborted(executionOptions)) return first;
349
+ return execute(command, executionOptions);
350
+ };
351
+ }
352
+
353
+ function settleLinkedInBrowserContext(): Promise<void> {
354
+ return new Promise((resolve) => {
355
+ setTimeout(resolve, 500);
356
+ });
357
+ }
358
+
359
+ async function finalizeBrowserSession(session: BrowserSession): Promise<void> {
360
+ const failures: unknown[] = [];
361
+ let closeVerified = false;
362
+ try {
363
+ await session.close();
364
+ closeVerified = true;
365
+ } catch (error) {
366
+ failures.push(error);
367
+ }
368
+ let cleanupVerified = false;
369
+ try {
370
+ await session.cleanup();
371
+ cleanupVerified = true;
372
+ } catch (error) {
373
+ failures.push(error);
374
+ }
375
+ if (closeVerified && cleanupVerified) return;
376
+ const cleanupEvidence = (
377
+ closeVerified
378
+ && !cleanupVerified
379
+ && session.cleanupResourceIdentity !== undefined
380
+ )
381
+ ? Object.freeze({
382
+ kind: "agent-browser-closed-artifacts-v1" as const,
383
+ resource: session.cleanupResourceIdentity,
384
+ })
385
+ : undefined;
386
+ throw new PreservedBrowserArtifactsError(
387
+ "LinkedIn profile-activity browser finalization failed; private artifacts were preserved",
388
+ session.recoveryHandle ?? "session=linkedin-profile-activity-runtime;artifacts=unknown",
389
+ new AggregateError(failures, "LinkedIn profile-activity browser finalization failed"),
390
+ cleanupEvidence,
391
+ );
392
+ }
393
+
394
+ function observedRequests(value: unknown): readonly ObservedBrowserRequest[] {
395
+ if (!isRecord(value) || !Array.isArray(value.requests) || value.requests.length > MAX_NETWORK_REQUESTS) {
396
+ throw new LinkedInFeedBrowserFailure(
397
+ "browser-envelope",
398
+ "LinkedIn profile-activity network observation returned a malformed bounded request list",
399
+ );
400
+ }
401
+ const requests: ObservedBrowserRequest[] = [];
402
+ for (const item of value.requests) {
403
+ if (
404
+ !isRecord(item)
405
+ || typeof item.requestId !== "string"
406
+ || item.requestId.length < 1
407
+ || item.requestId.length > 512
408
+ || /[\0\r\n]/u.test(item.requestId)
409
+ ) {
410
+ throw new LinkedInFeedBrowserFailure(
411
+ "browser-envelope",
412
+ "LinkedIn profile-activity network observation returned a malformed request",
413
+ );
414
+ }
415
+ requests.push(Object.freeze({
416
+ requestId: item.requestId,
417
+ method: item.method,
418
+ status: item.status,
419
+ url: item.url,
420
+ }));
421
+ }
422
+ return requests;
423
+ }
424
+
425
+ function observedRequestIds(
426
+ requests: readonly ObservedBrowserRequest[],
427
+ ): ReadonlySet<string> {
428
+ const ids = new Set<string>();
429
+ for (const request of requests) {
430
+ if (ids.has(request.requestId)) {
431
+ throw new LinkedInFeedBrowserFailure(
432
+ "browser-envelope",
433
+ "LinkedIn profile-activity network observation returned duplicate request identities",
434
+ );
435
+ }
436
+ ids.add(request.requestId);
437
+ }
438
+ return ids;
439
+ }
440
+
441
+ function currentProfileActivityUrl(
442
+ value: unknown,
443
+ expected: string,
444
+ ): void {
445
+ if (!isRecord(value) || typeof value.url !== "string" || value.url.length > 2_048) {
446
+ throw new LinkedInFeedBrowserFailure(
447
+ "browser-envelope",
448
+ "LinkedIn profile-activity browser omitted its current URL",
449
+ );
450
+ }
451
+ let current: URL;
452
+ try {
453
+ current = new URL(value.url);
454
+ } catch {
455
+ throw new LinkedInFeedBrowserFailure(
456
+ "browser-envelope",
457
+ "LinkedIn profile-activity browser returned a malformed current URL",
458
+ );
459
+ }
460
+ if (
461
+ current.origin === LINKEDIN_ORIGIN
462
+ && /^\/(?:authwall|checkpoint|login|uas\/login(?:-submit)?)(?:\/|$)/u.test(current.pathname)
463
+ ) {
464
+ throw new LinkedInFeedBrowserFailure(
465
+ "authwall",
466
+ "LinkedIn profile-activity browser reached the signed-out authwall",
467
+ );
468
+ }
469
+ if (
470
+ current.username !== ""
471
+ || current.password !== ""
472
+ || current.hash !== ""
473
+ || current.href !== expected
474
+ ) throw new LinkedInFeedBrowserFailure(
475
+ "response-envelope",
476
+ "LinkedIn profile-activity browser left its exact target page",
477
+ );
478
+ }
479
+
480
+ export async function createLinkedInFeedBrowserTransport(
481
+ auth: GhostgetAuth,
482
+ options: {
483
+ readonly timeoutMs: number;
484
+ readonly maxOutputBytes: number;
485
+ readonly operationDeadline?: WebSessionOperationDeadline;
486
+ readonly publishCleanupResource?: WebSessionCleanupResourcePublisher;
487
+ readonly dependencies?: Partial<LinkedInFeedBrowserDependencies>;
488
+ },
489
+ ): Promise<LinkedInFeedBrowserTransport> {
490
+ if (
491
+ !Number.isSafeInteger(options.maxOutputBytes)
492
+ || options.maxOutputBytes < 1
493
+ || options.maxOutputBytes > MAX_FEED_PAGE_BYTES
494
+ ) throw new Error("LinkedIn profile-activity browser output bound is invalid");
495
+ const createSession = options.dependencies?.createBrowserSession
496
+ ?? createBrowserSession;
497
+ const browserOutputBytes = encodedBodyBound(options.maxOutputBytes)
498
+ + BROWSER_ENVELOPE_BYTES;
499
+ const sessionOptions: CreateBrowserSessionOptions = {
500
+ allowCodeOwnedEvaluation: true,
501
+ allowCodeOwnedNetworkObservation: true,
502
+ headed: true,
503
+ maxOutputBytes: browserOutputBytes,
504
+ timeoutMs: options.timeoutMs,
505
+ dependencies: {
506
+ runCommand: linkedInFeedBrowserCommandRunner(
507
+ options.dependencies?.runCommand ?? runCommand,
508
+ options.dependencies?.settleContext ?? settleLinkedInBrowserContext,
509
+ auth.kind,
510
+ ),
511
+ },
512
+ ...(options.operationDeadline === undefined
513
+ ? {}
514
+ : { operationDeadline: options.operationDeadline }),
515
+ ...(options.publishCleanupResource === undefined
516
+ ? {}
517
+ : { publishCleanupResource: options.publishCleanupResource }),
518
+ };
519
+ let session: BrowserSession;
520
+ try {
521
+ session = await createSession(feedBrowserManifest, auth, sessionOptions);
522
+ } catch (error) {
523
+ if (error instanceof PreservedBrowserArtifactsError) throw error;
524
+ options.operationDeadline?.throwIfUnavailable(
525
+ "LinkedIn profile-activity browser startup",
526
+ );
527
+ throw new LinkedInFeedBrowserFailure(
528
+ "startup",
529
+ "LinkedIn profile-activity browser could not start its contained session",
530
+ );
531
+ }
532
+ let closed = false;
533
+ let state: BrowserReadState = "ready";
534
+ let boundPage: BoundProfileActivityPage | null = null;
535
+
536
+ const remainingTimeMs = (): number =>
537
+ options.operationDeadline?.remainingTimeMs() ?? options.timeoutMs;
538
+
539
+ const observeProfileActivityRequests = async (): Promise<readonly ObservedBrowserRequest[]> => {
540
+ const entries = await session.runBatch(
541
+ [["network", "requests", "--filter", LINKEDIN_PROFILE_ACTIVITY_QUERY_PREFIX]],
542
+ Math.min(remainingTimeMs(), 30_000),
543
+ 8 * 1024 * 1024,
544
+ );
545
+ const first = entries[0];
546
+ if (first === undefined) {
547
+ throw new LinkedInFeedBrowserFailure(
548
+ "browser-envelope",
549
+ "LinkedIn profile-activity browser omitted its network observation",
550
+ );
551
+ }
552
+ return observedRequests(browserResultData(first));
553
+ };
554
+
555
+ const run = async (
556
+ binding: BrowserReadBinding,
557
+ ): Promise<Readonly<Record<string, unknown>>> => {
558
+ if (closed) throw new Error("LinkedIn profile-activity browser transport is closed");
559
+ const source = browserReadEvaluationSource(binding);
560
+ let records: readonly Readonly<Record<string, unknown>>[];
561
+ try {
562
+ records = await session.runBatch(
563
+ [["eval", source]],
564
+ remainingTimeMs(),
565
+ Math.min(
566
+ encodedBodyBound(binding.maxBytes) + BROWSER_ENVELOPE_BYTES,
567
+ browserOutputBytes,
568
+ ),
569
+ );
570
+ } catch (error) {
571
+ if (error instanceof PreservedBrowserArtifactsError) throw error;
572
+ if (error instanceof LinkedInFeedBrowserFailure) throw error;
573
+ options.operationDeadline?.throwIfUnavailable(
574
+ "LinkedIn profile-activity browser operation",
575
+ );
576
+ if (hasNoDefaultExecutionContext(error)) {
577
+ throw new LinkedInFeedBrowserFailure(
578
+ "execution-context",
579
+ "LinkedIn profile-activity browser lost its reviewed execution context",
580
+ );
581
+ }
582
+ if (hasUnexpectedLinkedInOrigin(error)) {
583
+ throw new LinkedInFeedBrowserFailure(
584
+ "bootstrap",
585
+ "LinkedIn profile-activity browser was not on its reviewed signed-in origin",
586
+ );
587
+ }
588
+ throw classifiedBrowserCommandFailure(error);
589
+ }
590
+ const first = records[0];
591
+ if (first === undefined) {
592
+ throw new LinkedInFeedBrowserFailure(
593
+ "response-envelope",
594
+ "LinkedIn profile-activity browser omitted its response",
595
+ );
596
+ }
597
+ const result = browserEvaluationResult(first);
598
+ try {
599
+ exactKeys(
600
+ result,
601
+ [
602
+ "authWall",
603
+ "bodyBase64",
604
+ "bodyBytes",
605
+ "bodySha256",
606
+ "contentType",
607
+ "status",
608
+ ],
609
+ "LinkedIn profile-activity browser request",
610
+ );
611
+ } catch {
612
+ throw new LinkedInFeedBrowserFailure(
613
+ "response-envelope",
614
+ "LinkedIn profile-activity browser request returned an unexpected result shape",
615
+ );
616
+ }
617
+ if (
618
+ typeof result.authWall !== "boolean"
619
+ || typeof result.status !== "number"
620
+ || !Number.isSafeInteger(result.status)
621
+ || typeof result.contentType !== "string"
622
+ || result.contentType.length > 128
623
+ || (result.contentType !== "" && !LINKEDIN_RESPONSE_MEDIA_TYPE.test(result.contentType))
624
+ ) throw new LinkedInFeedBrowserFailure(
625
+ "response-envelope",
626
+ "LinkedIn profile-activity browser returned a malformed response category",
627
+ );
628
+ if (result.authWall) {
629
+ if (
630
+ !(
631
+ result.status === 0
632
+ || (result.status >= 300 && result.status <= 399)
633
+ )
634
+ || result.contentType !== ""
635
+ || result.bodyBase64 !== null
636
+ || result.bodyBytes !== 0
637
+ || result.bodySha256 !== null
638
+ ) throw new LinkedInFeedBrowserFailure(
639
+ "response-envelope",
640
+ "LinkedIn profile-activity browser returned a malformed authwall envelope",
641
+ );
642
+ throw new LinkedInFeedBrowserFailure(
643
+ "authwall",
644
+ "LinkedIn profile-activity browser reached the signed-out authwall",
645
+ );
646
+ }
647
+ if (result.status < 100 || result.status > 599) {
648
+ throw new LinkedInFeedBrowserFailure(
649
+ "response-envelope",
650
+ "LinkedIn profile-activity browser returned a malformed response category",
651
+ );
652
+ }
653
+ if (result.status >= 300 && result.status <= 399) {
654
+ throw new LinkedInFeedBrowserFailure(
655
+ "response-envelope",
656
+ "LinkedIn profile-activity browser returned a malformed redirect envelope",
657
+ );
658
+ }
659
+ if (result.status === 401 || result.status === 403) {
660
+ if (
661
+ result.bodyBase64 !== null
662
+ || result.bodyBytes !== 0
663
+ || result.bodySha256 !== null
664
+ ) throw new LinkedInFeedBrowserFailure(
665
+ "response-envelope",
666
+ "LinkedIn profile-activity browser returned a malformed rejection envelope",
667
+ );
668
+ throw new LinkedInFeedBrowserFailure(
669
+ "authwall",
670
+ "LinkedIn profile-activity browser reached the signed-out authwall",
671
+ );
672
+ }
673
+ if (
674
+ result.status !== 200
675
+ || (
676
+ result.contentType !== "application/vnd.linkedin.normalized+json+2.1"
677
+ && result.contentType !== "application/json"
678
+ )
679
+ ) {
680
+ if (
681
+ result.bodyBase64 !== null
682
+ || result.bodyBytes !== 0
683
+ || result.bodySha256 !== null
684
+ ) throw new LinkedInFeedBrowserFailure(
685
+ "response-envelope",
686
+ "LinkedIn profile-activity browser returned a malformed rejection envelope",
687
+ );
688
+ throw new LinkedInFeedBrowserResponseRejectedError(
689
+ result.status,
690
+ result.contentType,
691
+ );
692
+ }
693
+ return result;
694
+ };
695
+
696
+ const parseJsonBody = (
697
+ result: Readonly<Record<string, unknown>>,
698
+ maximumBytes: number,
699
+ label: string,
700
+ ): unknown => {
701
+ const text = decodedBody(result, maximumBytes);
702
+ try {
703
+ return JSON.parse(text) as unknown;
704
+ } catch {
705
+ throw new LinkedInFeedBrowserFailure(
706
+ "identity-json",
707
+ `${label} was not valid JSON`,
708
+ );
709
+ }
710
+ };
711
+
712
+ return Object.freeze({
713
+ currentIdentityResponse: async () => {
714
+ if (state !== "ready") {
715
+ throw new Error("LinkedIn profile-activity browser identity read is out of order");
716
+ }
717
+ const result = await run({
718
+ documentUrl: null,
719
+ kind: "json",
720
+ maxBytes: Math.min(MAX_IDENTITY_BYTES, options.maxOutputBytes),
721
+ path: "/voyager/api/me",
722
+ referrer: LINKEDIN_FEED_URL,
723
+ });
724
+ state = "identity";
725
+ return parseJsonBody(
726
+ result,
727
+ Math.min(MAX_IDENTITY_BYTES, options.maxOutputBytes),
728
+ "LinkedIn profile-activity browser identity response",
729
+ );
730
+ },
731
+ resolveProfileActivityBinding: async (vanity: string) => {
732
+ if (state !== "identity") {
733
+ throw new Error("LinkedIn profile-activity browser query observation is out of order");
734
+ }
735
+ const target = linkedInProfileActivityTargetFromVanity(vanity);
736
+ try {
737
+ const baselineRequestIds = observedRequestIds(
738
+ await observeProfileActivityRequests(),
739
+ );
740
+ await session.runBatch(
741
+ [["open", target.activityUrl]],
742
+ remainingTimeMs(),
743
+ 2 * 1024 * 1024,
744
+ );
745
+ await session.runBatch(
746
+ [["wait", "8000"]],
747
+ Math.min(remainingTimeMs(), 20_000),
748
+ 1024 * 1024,
749
+ );
750
+ const requests = await observeProfileActivityRequests();
751
+ observedRequestIds(requests);
752
+ const currentUrlEntries = await session.runBatch(
753
+ [["get", "url"]],
754
+ Math.min(remainingTimeMs(), 10_000),
755
+ 1024 * 1024,
756
+ );
757
+ const currentUrl = currentUrlEntries[0];
758
+ if (currentUrl === undefined) {
759
+ throw new LinkedInFeedBrowserFailure(
760
+ "browser-envelope",
761
+ "LinkedIn profile-activity browser omitted its current URL",
762
+ );
763
+ }
764
+ currentProfileActivityUrl(browserResultData(currentUrl), target.activityUrl);
765
+ const bounded = requests.flatMap((request) => {
766
+ if (baselineRequestIds.has(request.requestId)) return [];
767
+ if (typeof request.url !== "string" || request.url.length > MAX_REQUEST_URL_CHARACTERS) {
768
+ return [];
769
+ }
770
+ return [{
771
+ method: request.method,
772
+ status: request.status,
773
+ url: request.url,
774
+ }];
775
+ });
776
+ const binding = resolveLinkedInProfileActivityBinding(bounded);
777
+ boundPage = Object.freeze({
778
+ binding,
779
+ targetUrl: target.activityUrl,
780
+ vanity: target.slug,
781
+ });
782
+ state = "bound";
783
+ return binding;
784
+ } catch (error) {
785
+ if (error instanceof PreservedBrowserArtifactsError) throw error;
786
+ if (error instanceof LinkedInFeedBrowserFailure) throw error;
787
+ options.operationDeadline?.throwIfUnavailable(
788
+ "LinkedIn profile-activity query observation",
789
+ );
790
+ throw new LinkedInFeedBrowserFailure(
791
+ "browser-command",
792
+ "LinkedIn current registered query was not observed",
793
+ );
794
+ }
795
+ },
796
+ readProfileActivityPage: async (input: {
797
+ readonly count: number;
798
+ readonly start: number;
799
+ }) => {
800
+ if (state !== "bound" || boundPage === null) {
801
+ throw new Error("LinkedIn profile-activity browser page read is out of order");
802
+ }
803
+ const request = {
804
+ queryId: boundPage.binding.queryId,
805
+ profileUrn: boundPage.binding.profileUrn,
806
+ count: input.count,
807
+ start: input.start,
808
+ } as const;
809
+ const url = linkedInProfileActivityPageUrl(request);
810
+ assertLinkedInProfileActivityRequest({ method: "GET", url }, request);
811
+ const path = `${url.pathname}${url.search}`;
812
+ const result = await run({
813
+ documentUrl: boundPage.targetUrl,
814
+ kind: "json",
815
+ maxBytes: options.maxOutputBytes,
816
+ path,
817
+ referrer: boundPage.targetUrl,
818
+ });
819
+ state = "bound";
820
+ return parseJsonBody(
821
+ result,
822
+ options.maxOutputBytes,
823
+ "LinkedIn profile-activity browser page response",
824
+ );
825
+ },
826
+ close: async () => {
827
+ if (closed) return;
828
+ closed = true;
829
+ await finalizeBrowserSession(session);
830
+ },
831
+ });
832
+ }