@lhremote/core 0.8.0 → 0.9.0

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 (438) hide show
  1. package/dist/cdp/app-discovery.d.ts +57 -0
  2. package/dist/cdp/app-discovery.d.ts.map +1 -1
  3. package/dist/cdp/app-discovery.js +90 -7
  4. package/dist/cdp/app-discovery.js.map +1 -1
  5. package/dist/cdp/app-discovery.test.js +180 -9
  6. package/dist/cdp/app-discovery.test.js.map +1 -1
  7. package/dist/cdp/client.d.ts +8 -1
  8. package/dist/cdp/client.d.ts.map +1 -1
  9. package/dist/cdp/client.js +8 -1
  10. package/dist/cdp/client.js.map +1 -1
  11. package/dist/cdp/discovery.d.ts.map +1 -1
  12. package/dist/cdp/discovery.js +5 -1
  13. package/dist/cdp/discovery.js.map +1 -1
  14. package/dist/cdp/discovery.test.js +7 -0
  15. package/dist/cdp/discovery.test.js.map +1 -1
  16. package/dist/cdp/index.d.ts +1 -1
  17. package/dist/cdp/index.d.ts.map +1 -1
  18. package/dist/cdp/index.js +1 -1
  19. package/dist/cdp/index.js.map +1 -1
  20. package/dist/cdp/instance-discovery.d.ts.map +1 -1
  21. package/dist/cdp/instance-discovery.js +25 -10
  22. package/dist/cdp/instance-discovery.js.map +1 -1
  23. package/dist/cdp/instance-discovery.test.js +17 -0
  24. package/dist/cdp/instance-discovery.test.js.map +1 -1
  25. package/dist/cdp/testing/launch-chromium.d.ts +1 -1
  26. package/dist/cdp/testing/launch-chromium.js +2 -2
  27. package/dist/db/client.d.ts.map +1 -1
  28. package/dist/db/client.js +3 -0
  29. package/dist/db/client.js.map +1 -1
  30. package/dist/db/repositories/collection-list.integration.test.js +6 -4
  31. package/dist/db/repositories/collection-list.integration.test.js.map +1 -1
  32. package/dist/index.d.ts +6 -6
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +6 -6
  35. package/dist/index.js.map +1 -1
  36. package/dist/linkedin/dom-automation-retry.test.d.ts +2 -0
  37. package/dist/linkedin/dom-automation-retry.test.d.ts.map +1 -0
  38. package/dist/linkedin/dom-automation-retry.test.js +51 -0
  39. package/dist/linkedin/dom-automation-retry.test.js.map +1 -0
  40. package/dist/linkedin/dom-automation.d.ts +160 -3
  41. package/dist/linkedin/dom-automation.d.ts.map +1 -1
  42. package/dist/linkedin/dom-automation.js +489 -14
  43. package/dist/linkedin/dom-automation.js.map +1 -1
  44. package/dist/linkedin/humanized-mouse.d.ts +69 -0
  45. package/dist/linkedin/humanized-mouse.d.ts.map +1 -0
  46. package/dist/linkedin/humanized-mouse.js +109 -0
  47. package/dist/linkedin/humanized-mouse.js.map +1 -0
  48. package/dist/linkedin/index.d.ts +2 -1
  49. package/dist/linkedin/index.d.ts.map +1 -1
  50. package/dist/linkedin/index.js +2 -1
  51. package/dist/linkedin/index.js.map +1 -1
  52. package/dist/operations/add-people-to-collection.d.ts +1 -1
  53. package/dist/operations/add-people-to-collection.d.ts.map +1 -1
  54. package/dist/operations/add-people-to-collection.js +3 -6
  55. package/dist/operations/add-people-to-collection.js.map +1 -1
  56. package/dist/operations/build-linkedin-url.test.d.ts +2 -0
  57. package/dist/operations/build-linkedin-url.test.d.ts.map +1 -0
  58. package/dist/operations/build-linkedin-url.test.js +158 -0
  59. package/dist/operations/build-linkedin-url.test.js.map +1 -0
  60. package/dist/operations/campaign-add-action.d.ts +1 -1
  61. package/dist/operations/campaign-add-action.d.ts.map +1 -1
  62. package/dist/operations/campaign-add-action.js +3 -6
  63. package/dist/operations/campaign-add-action.js.map +1 -1
  64. package/dist/operations/campaign-create.d.ts +1 -1
  65. package/dist/operations/campaign-create.d.ts.map +1 -1
  66. package/dist/operations/campaign-create.js +3 -6
  67. package/dist/operations/campaign-create.js.map +1 -1
  68. package/dist/operations/campaign-delete.d.ts +1 -1
  69. package/dist/operations/campaign-delete.d.ts.map +1 -1
  70. package/dist/operations/campaign-delete.js +3 -6
  71. package/dist/operations/campaign-delete.js.map +1 -1
  72. package/dist/operations/campaign-erase.d.ts +1 -1
  73. package/dist/operations/campaign-erase.d.ts.map +1 -1
  74. package/dist/operations/campaign-erase.js +3 -6
  75. package/dist/operations/campaign-erase.js.map +1 -1
  76. package/dist/operations/campaign-exclude-add.d.ts +1 -1
  77. package/dist/operations/campaign-exclude-add.d.ts.map +1 -1
  78. package/dist/operations/campaign-exclude-add.js +3 -6
  79. package/dist/operations/campaign-exclude-add.js.map +1 -1
  80. package/dist/operations/campaign-exclude-list.d.ts +1 -1
  81. package/dist/operations/campaign-exclude-list.d.ts.map +1 -1
  82. package/dist/operations/campaign-exclude-list.js +3 -6
  83. package/dist/operations/campaign-exclude-list.js.map +1 -1
  84. package/dist/operations/campaign-exclude-remove.d.ts +1 -1
  85. package/dist/operations/campaign-exclude-remove.d.ts.map +1 -1
  86. package/dist/operations/campaign-exclude-remove.js +3 -6
  87. package/dist/operations/campaign-exclude-remove.js.map +1 -1
  88. package/dist/operations/campaign-export.d.ts +1 -1
  89. package/dist/operations/campaign-export.d.ts.map +1 -1
  90. package/dist/operations/campaign-export.js +3 -6
  91. package/dist/operations/campaign-export.js.map +1 -1
  92. package/dist/operations/campaign-get.d.ts +1 -1
  93. package/dist/operations/campaign-get.d.ts.map +1 -1
  94. package/dist/operations/campaign-get.js +3 -6
  95. package/dist/operations/campaign-get.js.map +1 -1
  96. package/dist/operations/campaign-list-people.d.ts +1 -1
  97. package/dist/operations/campaign-list-people.d.ts.map +1 -1
  98. package/dist/operations/campaign-list-people.js +3 -6
  99. package/dist/operations/campaign-list-people.js.map +1 -1
  100. package/dist/operations/campaign-list.d.ts +1 -1
  101. package/dist/operations/campaign-list.d.ts.map +1 -1
  102. package/dist/operations/campaign-list.js +3 -6
  103. package/dist/operations/campaign-list.js.map +1 -1
  104. package/dist/operations/campaign-move-next.d.ts +1 -1
  105. package/dist/operations/campaign-move-next.d.ts.map +1 -1
  106. package/dist/operations/campaign-move-next.js +3 -6
  107. package/dist/operations/campaign-move-next.js.map +1 -1
  108. package/dist/operations/campaign-remove-action.d.ts +1 -1
  109. package/dist/operations/campaign-remove-action.d.ts.map +1 -1
  110. package/dist/operations/campaign-remove-action.js +3 -6
  111. package/dist/operations/campaign-remove-action.js.map +1 -1
  112. package/dist/operations/campaign-remove-people.d.ts +1 -1
  113. package/dist/operations/campaign-remove-people.d.ts.map +1 -1
  114. package/dist/operations/campaign-remove-people.js +3 -6
  115. package/dist/operations/campaign-remove-people.js.map +1 -1
  116. package/dist/operations/campaign-reorder-actions.d.ts +1 -1
  117. package/dist/operations/campaign-reorder-actions.d.ts.map +1 -1
  118. package/dist/operations/campaign-reorder-actions.js +3 -6
  119. package/dist/operations/campaign-reorder-actions.js.map +1 -1
  120. package/dist/operations/campaign-retry.d.ts +1 -1
  121. package/dist/operations/campaign-retry.d.ts.map +1 -1
  122. package/dist/operations/campaign-retry.js +3 -6
  123. package/dist/operations/campaign-retry.js.map +1 -1
  124. package/dist/operations/campaign-start.d.ts +1 -1
  125. package/dist/operations/campaign-start.d.ts.map +1 -1
  126. package/dist/operations/campaign-start.js +3 -6
  127. package/dist/operations/campaign-start.js.map +1 -1
  128. package/dist/operations/campaign-statistics.d.ts +1 -1
  129. package/dist/operations/campaign-statistics.d.ts.map +1 -1
  130. package/dist/operations/campaign-statistics.js +3 -6
  131. package/dist/operations/campaign-statistics.js.map +1 -1
  132. package/dist/operations/campaign-status.d.ts +1 -1
  133. package/dist/operations/campaign-status.d.ts.map +1 -1
  134. package/dist/operations/campaign-status.js +3 -6
  135. package/dist/operations/campaign-status.js.map +1 -1
  136. package/dist/operations/campaign-stop.d.ts +1 -1
  137. package/dist/operations/campaign-stop.d.ts.map +1 -1
  138. package/dist/operations/campaign-stop.js +3 -6
  139. package/dist/operations/campaign-stop.js.map +1 -1
  140. package/dist/operations/campaign-update-action.d.ts +1 -1
  141. package/dist/operations/campaign-update-action.d.ts.map +1 -1
  142. package/dist/operations/campaign-update-action.js +3 -6
  143. package/dist/operations/campaign-update-action.js.map +1 -1
  144. package/dist/operations/campaign-update.d.ts +1 -1
  145. package/dist/operations/campaign-update.d.ts.map +1 -1
  146. package/dist/operations/campaign-update.js +3 -6
  147. package/dist/operations/campaign-update.js.map +1 -1
  148. package/dist/operations/check-replies.d.ts +3 -1
  149. package/dist/operations/check-replies.d.ts.map +1 -1
  150. package/dist/operations/check-replies.js +132 -17
  151. package/dist/operations/check-replies.js.map +1 -1
  152. package/dist/operations/check-replies.test.js +177 -17
  153. package/dist/operations/check-replies.test.js.map +1 -1
  154. package/dist/operations/collect-people.d.ts +1 -1
  155. package/dist/operations/collect-people.d.ts.map +1 -1
  156. package/dist/operations/collect-people.js +3 -6
  157. package/dist/operations/collect-people.js.map +1 -1
  158. package/dist/operations/comment-on-post.d.ts +4 -1
  159. package/dist/operations/comment-on-post.d.ts.map +1 -1
  160. package/dist/operations/comment-on-post.js +15 -19
  161. package/dist/operations/comment-on-post.js.map +1 -1
  162. package/dist/operations/comment-on-post.test.js +8 -6
  163. package/dist/operations/comment-on-post.test.js.map +1 -1
  164. package/dist/operations/create-collection.d.ts +1 -1
  165. package/dist/operations/create-collection.d.ts.map +1 -1
  166. package/dist/operations/create-collection.js +3 -6
  167. package/dist/operations/create-collection.js.map +1 -1
  168. package/dist/operations/delete-collection.d.ts +1 -1
  169. package/dist/operations/delete-collection.d.ts.map +1 -1
  170. package/dist/operations/delete-collection.js +3 -6
  171. package/dist/operations/delete-collection.js.map +1 -1
  172. package/dist/operations/dismiss-errors.d.ts +7 -4
  173. package/dist/operations/dismiss-errors.d.ts.map +1 -1
  174. package/dist/operations/dismiss-errors.js +52 -33
  175. package/dist/operations/dismiss-errors.js.map +1 -1
  176. package/dist/operations/dismiss-errors.test.js +53 -6
  177. package/dist/operations/dismiss-errors.test.js.map +1 -1
  178. package/dist/operations/endorse-skills.test.d.ts +2 -0
  179. package/dist/operations/endorse-skills.test.d.ts.map +1 -0
  180. package/dist/operations/endorse-skills.test.js +70 -0
  181. package/dist/operations/endorse-skills.test.js.map +1 -0
  182. package/dist/operations/enrich-profile.test.d.ts +2 -0
  183. package/dist/operations/enrich-profile.test.d.ts.map +1 -0
  184. package/dist/operations/enrich-profile.test.js +73 -0
  185. package/dist/operations/enrich-profile.test.js.map +1 -0
  186. package/dist/operations/ephemeral-action.d.ts +1 -1
  187. package/dist/operations/ephemeral-action.d.ts.map +1 -1
  188. package/dist/operations/ephemeral-action.js +3 -6
  189. package/dist/operations/ephemeral-action.js.map +1 -1
  190. package/dist/operations/ephemeral-action.test.d.ts +2 -0
  191. package/dist/operations/ephemeral-action.test.d.ts.map +1 -0
  192. package/dist/operations/ephemeral-action.test.js +140 -0
  193. package/dist/operations/ephemeral-action.test.js.map +1 -0
  194. package/dist/operations/follow-person.test.d.ts +2 -0
  195. package/dist/operations/follow-person.test.d.ts.map +1 -0
  196. package/dist/operations/follow-person.test.js +57 -0
  197. package/dist/operations/follow-person.test.js.map +1 -0
  198. package/dist/operations/get-action-budget.d.ts +1 -1
  199. package/dist/operations/get-action-budget.d.ts.map +1 -1
  200. package/dist/operations/get-action-budget.js +3 -6
  201. package/dist/operations/get-action-budget.js.map +1 -1
  202. package/dist/operations/get-errors.d.ts +5 -1
  203. package/dist/operations/get-errors.d.ts.map +1 -1
  204. package/dist/operations/get-errors.js +55 -33
  205. package/dist/operations/get-errors.js.map +1 -1
  206. package/dist/operations/get-errors.test.js +54 -55
  207. package/dist/operations/get-errors.test.js.map +1 -1
  208. package/dist/operations/get-feed.d.ts +83 -4
  209. package/dist/operations/get-feed.d.ts.map +1 -1
  210. package/dist/operations/get-feed.js +400 -153
  211. package/dist/operations/get-feed.js.map +1 -1
  212. package/dist/operations/get-feed.test.js +416 -190
  213. package/dist/operations/get-feed.test.js.map +1 -1
  214. package/dist/operations/get-post-engagers.d.ts +6 -3
  215. package/dist/operations/get-post-engagers.d.ts.map +1 -1
  216. package/dist/operations/get-post-engagers.js +278 -78
  217. package/dist/operations/get-post-engagers.js.map +1 -1
  218. package/dist/operations/get-post-engagers.test.js +292 -14
  219. package/dist/operations/get-post-engagers.test.js.map +1 -1
  220. package/dist/operations/get-post-stats.d.ts +8 -3
  221. package/dist/operations/get-post-stats.d.ts.map +1 -1
  222. package/dist/operations/get-post-stats.js +101 -37
  223. package/dist/operations/get-post-stats.js.map +1 -1
  224. package/dist/operations/get-post-stats.test.js +137 -2
  225. package/dist/operations/get-post-stats.test.js.map +1 -1
  226. package/dist/operations/get-post.d.ts +9 -150
  227. package/dist/operations/get-post.d.ts.map +1 -1
  228. package/dist/operations/get-post.js +356 -210
  229. package/dist/operations/get-post.js.map +1 -1
  230. package/dist/operations/get-post.test.js +210 -387
  231. package/dist/operations/get-post.test.js.map +1 -1
  232. package/dist/operations/get-profile-activity.d.ts +13 -92
  233. package/dist/operations/get-profile-activity.d.ts.map +1 -1
  234. package/dist/operations/get-profile-activity.js +305 -105
  235. package/dist/operations/get-profile-activity.js.map +1 -1
  236. package/dist/operations/get-profile-activity.test.js +277 -158
  237. package/dist/operations/get-profile-activity.test.js.map +1 -1
  238. package/dist/operations/get-throttle-status.d.ts +1 -1
  239. package/dist/operations/get-throttle-status.d.ts.map +1 -1
  240. package/dist/operations/get-throttle-status.js +4 -10
  241. package/dist/operations/get-throttle-status.js.map +1 -1
  242. package/dist/operations/import-people-from-collection.d.ts +1 -1
  243. package/dist/operations/import-people-from-collection.d.ts.map +1 -1
  244. package/dist/operations/import-people-from-collection.js +3 -6
  245. package/dist/operations/import-people-from-collection.js.map +1 -1
  246. package/dist/operations/import-people-from-urls.d.ts +1 -1
  247. package/dist/operations/import-people-from-urls.d.ts.map +1 -1
  248. package/dist/operations/import-people-from-urls.js +3 -6
  249. package/dist/operations/import-people-from-urls.js.map +1 -1
  250. package/dist/operations/index.d.ts +2 -2
  251. package/dist/operations/index.d.ts.map +1 -1
  252. package/dist/operations/index.js +2 -1
  253. package/dist/operations/index.js.map +1 -1
  254. package/dist/operations/like-person-posts.test.d.ts +2 -0
  255. package/dist/operations/like-person-posts.test.d.ts.map +1 -0
  256. package/dist/operations/like-person-posts.test.js +103 -0
  257. package/dist/operations/like-person-posts.test.js.map +1 -0
  258. package/dist/operations/list-collections.d.ts +1 -1
  259. package/dist/operations/list-collections.d.ts.map +1 -1
  260. package/dist/operations/list-collections.js +3 -6
  261. package/dist/operations/list-collections.js.map +1 -1
  262. package/dist/operations/message-person.test.d.ts +2 -0
  263. package/dist/operations/message-person.test.d.ts.map +1 -0
  264. package/dist/operations/message-person.test.js +108 -0
  265. package/dist/operations/message-person.test.js.map +1 -0
  266. package/dist/operations/navigate-away.d.ts +14 -0
  267. package/dist/operations/navigate-away.d.ts.map +1 -0
  268. package/dist/operations/navigate-away.js +24 -0
  269. package/dist/operations/navigate-away.js.map +1 -0
  270. package/dist/operations/navigate-away.test.d.ts +2 -0
  271. package/dist/operations/navigate-away.test.d.ts.map +1 -0
  272. package/dist/operations/navigate-away.test.js +51 -0
  273. package/dist/operations/navigate-away.test.js.map +1 -0
  274. package/dist/operations/query-messages.d.ts +1 -1
  275. package/dist/operations/query-messages.d.ts.map +1 -1
  276. package/dist/operations/query-messages.js +3 -6
  277. package/dist/operations/query-messages.js.map +1 -1
  278. package/dist/operations/react-to-post.d.ts +3 -0
  279. package/dist/operations/react-to-post.d.ts.map +1 -1
  280. package/dist/operations/react-to-post.js +13 -15
  281. package/dist/operations/react-to-post.js.map +1 -1
  282. package/dist/operations/react-to-post.test.js +8 -5
  283. package/dist/operations/react-to-post.test.js.map +1 -1
  284. package/dist/operations/remove-connection.test.d.ts +2 -0
  285. package/dist/operations/remove-connection.test.d.ts.map +1 -0
  286. package/dist/operations/remove-connection.test.js +45 -0
  287. package/dist/operations/remove-connection.test.js.map +1 -0
  288. package/dist/operations/remove-people-from-collection.d.ts +1 -1
  289. package/dist/operations/remove-people-from-collection.d.ts.map +1 -1
  290. package/dist/operations/remove-people-from-collection.js +3 -6
  291. package/dist/operations/remove-people-from-collection.js.map +1 -1
  292. package/dist/operations/resolve-linkedin-entity.d.ts.map +1 -1
  293. package/dist/operations/resolve-linkedin-entity.js +2 -2
  294. package/dist/operations/resolve-linkedin-entity.js.map +1 -1
  295. package/dist/operations/resolve-linkedin-entity.test.d.ts +2 -0
  296. package/dist/operations/resolve-linkedin-entity.test.d.ts.map +1 -0
  297. package/dist/operations/resolve-linkedin-entity.test.js +343 -0
  298. package/dist/operations/resolve-linkedin-entity.test.js.map +1 -0
  299. package/dist/operations/scrape-messaging-history.d.ts +2 -1
  300. package/dist/operations/scrape-messaging-history.d.ts.map +1 -1
  301. package/dist/operations/scrape-messaging-history.js +121 -18
  302. package/dist/operations/scrape-messaging-history.js.map +1 -1
  303. package/dist/operations/scrape-messaging-history.test.js +134 -12
  304. package/dist/operations/scrape-messaging-history.test.js.map +1 -1
  305. package/dist/operations/search-posts.d.ts +20 -112
  306. package/dist/operations/search-posts.d.ts.map +1 -1
  307. package/dist/operations/search-posts.js +369 -170
  308. package/dist/operations/search-posts.js.map +1 -1
  309. package/dist/operations/search-posts.test.js +273 -234
  310. package/dist/operations/search-posts.test.js.map +1 -1
  311. package/dist/operations/send-inmail.test.d.ts +2 -0
  312. package/dist/operations/send-inmail.test.d.ts.map +1 -0
  313. package/dist/operations/send-inmail.test.js +108 -0
  314. package/dist/operations/send-inmail.test.js.map +1 -0
  315. package/dist/operations/send-invite.test.d.ts +2 -0
  316. package/dist/operations/send-invite.test.d.ts.map +1 -0
  317. package/dist/operations/send-invite.test.js +59 -0
  318. package/dist/operations/send-invite.test.js.map +1 -0
  319. package/dist/operations/types.d.ts +24 -1
  320. package/dist/operations/types.d.ts.map +1 -1
  321. package/dist/operations/types.js +13 -1
  322. package/dist/operations/types.js.map +1 -1
  323. package/dist/operations/visit-profile.d.ts +1 -1
  324. package/dist/operations/visit-profile.d.ts.map +1 -1
  325. package/dist/operations/visit-profile.js +3 -6
  326. package/dist/operations/visit-profile.js.map +1 -1
  327. package/dist/services/account-resolution.d.ts +9 -4
  328. package/dist/services/account-resolution.d.ts.map +1 -1
  329. package/dist/services/account-resolution.js +60 -5
  330. package/dist/services/account-resolution.js.map +1 -1
  331. package/dist/services/app.d.ts +6 -2
  332. package/dist/services/app.d.ts.map +1 -1
  333. package/dist/services/app.js +18 -6
  334. package/dist/services/app.js.map +1 -1
  335. package/dist/services/app.test.js +41 -7
  336. package/dist/services/app.test.js.map +1 -1
  337. package/dist/services/campaign.d.ts +34 -2
  338. package/dist/services/campaign.d.ts.map +1 -1
  339. package/dist/services/campaign.js +108 -1
  340. package/dist/services/campaign.js.map +1 -1
  341. package/dist/services/errors.d.ts +2 -1
  342. package/dist/services/errors.d.ts.map +1 -1
  343. package/dist/services/errors.js +6 -3
  344. package/dist/services/errors.js.map +1 -1
  345. package/dist/services/index.d.ts +0 -1
  346. package/dist/services/index.d.ts.map +1 -1
  347. package/dist/services/index.js +0 -1
  348. package/dist/services/index.js.map +1 -1
  349. package/dist/services/instance-context.d.ts +5 -1
  350. package/dist/services/instance-context.d.ts.map +1 -1
  351. package/dist/services/instance-context.js +87 -28
  352. package/dist/services/instance-context.js.map +1 -1
  353. package/dist/services/instance-context.test.js +5 -1
  354. package/dist/services/instance-context.test.js.map +1 -1
  355. package/dist/services/instance-lifecycle.d.ts.map +1 -1
  356. package/dist/services/instance-lifecycle.js +32 -1
  357. package/dist/services/instance-lifecycle.js.map +1 -1
  358. package/dist/services/instance-lifecycle.test.js +52 -1
  359. package/dist/services/instance-lifecycle.test.js.map +1 -1
  360. package/dist/services/instance.d.ts +19 -7
  361. package/dist/services/instance.d.ts.map +1 -1
  362. package/dist/services/instance.js +56 -17
  363. package/dist/services/instance.js.map +1 -1
  364. package/dist/services/instance.test.js +65 -0
  365. package/dist/services/instance.test.js.map +1 -1
  366. package/dist/services/launcher.d.ts +47 -3
  367. package/dist/services/launcher.d.ts.map +1 -1
  368. package/dist/services/launcher.js +205 -33
  369. package/dist/services/launcher.js.map +1 -1
  370. package/dist/services/launcher.test.js +133 -6
  371. package/dist/services/launcher.test.js.map +1 -1
  372. package/dist/services/status.d.ts +6 -2
  373. package/dist/services/status.d.ts.map +1 -1
  374. package/dist/services/status.js +67 -34
  375. package/dist/services/status.js.map +1 -1
  376. package/dist/services/status.test.js +9 -2
  377. package/dist/services/status.test.js.map +1 -1
  378. package/dist/testing/e2e-helpers.d.ts +23 -1
  379. package/dist/testing/e2e-helpers.d.ts.map +1 -1
  380. package/dist/testing/e2e-helpers.js +110 -5
  381. package/dist/testing/e2e-helpers.js.map +1 -1
  382. package/dist/testing/index.d.ts +1 -1
  383. package/dist/testing/index.d.ts.map +1 -1
  384. package/dist/testing/index.js +1 -1
  385. package/dist/testing/index.js.map +1 -1
  386. package/dist/types/account.d.ts +1 -1
  387. package/dist/types/feed.d.ts +1 -3
  388. package/dist/types/feed.d.ts.map +1 -1
  389. package/dist/types/index.d.ts +0 -1
  390. package/dist/types/index.d.ts.map +1 -1
  391. package/dist/utils/cdp-port.d.ts.map +1 -1
  392. package/dist/utils/cdp-port.js +3 -1
  393. package/dist/utils/cdp-port.js.map +1 -1
  394. package/dist/utils/cdp-port.test.js +1 -1
  395. package/dist/utils/cdp-port.test.js.map +1 -1
  396. package/dist/utils/delay.d.ts +79 -0
  397. package/dist/utils/delay.d.ts.map +1 -1
  398. package/dist/utils/delay.js +118 -0
  399. package/dist/utils/delay.js.map +1 -1
  400. package/dist/utils/delay.test.js +111 -1
  401. package/dist/utils/delay.test.js.map +1 -1
  402. package/dist/utils/index.d.ts +2 -1
  403. package/dist/utils/index.d.ts.map +1 -1
  404. package/dist/utils/index.js +2 -1
  405. package/dist/utils/index.js.map +1 -1
  406. package/dist/utils/session-pacer.d.ts +27 -0
  407. package/dist/utils/session-pacer.d.ts.map +1 -0
  408. package/dist/utils/session-pacer.js +55 -0
  409. package/dist/utils/session-pacer.js.map +1 -0
  410. package/dist/utils/session-pacer.test.d.ts +2 -0
  411. package/dist/utils/session-pacer.test.d.ts.map +1 -0
  412. package/dist/utils/session-pacer.test.js +111 -0
  413. package/dist/utils/session-pacer.test.js.map +1 -0
  414. package/package.json +1 -1
  415. package/dist/linkedin/__tests__/selectors.integration.test.d.ts +0 -2
  416. package/dist/linkedin/__tests__/selectors.integration.test.d.ts.map +0 -1
  417. package/dist/linkedin/__tests__/selectors.integration.test.js +0 -258
  418. package/dist/linkedin/__tests__/selectors.integration.test.js.map +0 -1
  419. package/dist/types/search-posts.d.ts +0 -22
  420. package/dist/types/search-posts.d.ts.map +0 -1
  421. package/dist/types/search-posts.js +0 -4
  422. package/dist/types/search-posts.js.map +0 -1
  423. package/dist/voyager/index.d.ts +0 -2
  424. package/dist/voyager/index.d.ts.map +0 -1
  425. package/dist/voyager/index.js +0 -4
  426. package/dist/voyager/index.js.map +0 -1
  427. package/dist/voyager/interceptor.d.ts +0 -100
  428. package/dist/voyager/interceptor.d.ts.map +0 -1
  429. package/dist/voyager/interceptor.integration.test.d.ts +0 -2
  430. package/dist/voyager/interceptor.integration.test.d.ts.map +0 -1
  431. package/dist/voyager/interceptor.integration.test.js +0 -89
  432. package/dist/voyager/interceptor.integration.test.js.map +0 -1
  433. package/dist/voyager/interceptor.js +0 -235
  434. package/dist/voyager/interceptor.js.map +0 -1
  435. package/dist/voyager/interceptor.test.d.ts +0 -2
  436. package/dist/voyager/interceptor.test.d.ts.map +0 -1
  437. package/dist/voyager/interceptor.test.js +0 -372
  438. package/dist/voyager/interceptor.test.js.map +0 -1
@@ -7,15 +7,90 @@ vi.mock("../cdp/discovery.js", () => ({
7
7
  vi.mock("../cdp/client.js", () => ({
8
8
  CDPClient: vi.fn(),
9
9
  }));
10
- vi.mock("../voyager/interceptor.js", () => ({
11
- VoyagerInterceptor: vi.fn(),
10
+ vi.mock("./navigate-away.js", () => ({
11
+ navigateAwayIf: vi.fn().mockResolvedValue(undefined),
12
+ }));
13
+ vi.mock("../utils/delay.js", () => ({
14
+ delay: vi.fn().mockResolvedValue(undefined),
15
+ gaussianDelay: vi.fn().mockResolvedValue(undefined),
16
+ gaussianBetween: vi.fn().mockReturnValue(800),
17
+ maybeHesitate: vi.fn().mockResolvedValue(undefined),
18
+ maybeBreak: vi.fn().mockResolvedValue(undefined),
19
+ simulateReadingTime: vi.fn().mockResolvedValue(undefined),
12
20
  }));
13
21
  import { discoverTargets } from "../cdp/discovery.js";
14
22
  import { CDPClient } from "../cdp/client.js";
15
- import { VoyagerInterceptor } from "../voyager/interceptor.js";
16
- import { getFeed } from "./get-feed.js";
23
+ import { getFeed, extractHashtags, parseTimestamp } from "./get-feed.js";
17
24
  const CDP_PORT = 9222;
18
- function setupMocks(body, status = 200) {
25
+ /**
26
+ * Build a minimal raw DOM post object.
27
+ */
28
+ function rawPost(overrides = {}) {
29
+ return {
30
+ url: overrides.url ?? "https://www.linkedin.com/feed/update/urn:li:activity:123/",
31
+ authorName: null,
32
+ authorHeadline: null,
33
+ authorProfileUrl: null,
34
+ text: null,
35
+ mediaType: null,
36
+ reactionCount: 0,
37
+ commentCount: 0,
38
+ shareCount: 0,
39
+ timestamp: null,
40
+ ...overrides,
41
+ };
42
+ }
43
+ /**
44
+ * Create a script-aware evaluate mock that handles the getFeed call sequence:
45
+ * 1. waitForFeedLoad → truthy when posts exist
46
+ * 2. SCRAPE_FEED_POSTS_SCRIPT → posts array (may repeat on scroll)
47
+ * 3. Clipboard interceptor install → void
48
+ * 4. Per-post URL capture: reset → void, scroll → void, menu click → true,
49
+ * copy-link click → void, clipboard read → url string
50
+ */
51
+ function createEvaluateMock(scrapedPosts) {
52
+ let urlIdx = 0;
53
+ return vi.fn().mockImplementation((script) => {
54
+ const s = String(script);
55
+ // Order matters: check most specific patterns first
56
+ // Scrape script: long script with parseCount function
57
+ if (s.includes("parseCount")) {
58
+ return Promise.resolve(scrapedPosts);
59
+ }
60
+ // Clipboard interceptor install
61
+ if (s.includes("navigator.clipboard.writeText")) {
62
+ return Promise.resolve(undefined);
63
+ }
64
+ // Clipboard reset
65
+ if (s.includes("__capturedClipboard = null")) {
66
+ return Promise.resolve(undefined);
67
+ }
68
+ // "Copy link to post" menu item click
69
+ if (s.includes("Copy link to post")) {
70
+ return Promise.resolve(undefined);
71
+ }
72
+ // Read captured clipboard URL (exact match — not the reset)
73
+ if (s === "window.__capturedClipboard") {
74
+ const url = scrapedPosts[urlIdx]?.url ?? null;
75
+ urlIdx++;
76
+ return Promise.resolve(url);
77
+ }
78
+ // Menu button click (split from scroll): contains btn.click()
79
+ if (s.includes("btn.click()")) {
80
+ return Promise.resolve(true);
81
+ }
82
+ // Humanized scroll-to-element fallback: contains scrollIntoView
83
+ if (s.includes("scrollIntoView")) {
84
+ return Promise.resolve(undefined);
85
+ }
86
+ // waitForFeedLoad: short script with mainFeed check — always pass
87
+ if (s.includes("mainFeed")) {
88
+ return Promise.resolve(true);
89
+ }
90
+ return Promise.resolve(null);
91
+ });
92
+ }
93
+ function setupMocks(scrapedPosts = []) {
19
94
  vi.mocked(discoverTargets).mockResolvedValue([
20
95
  {
21
96
  id: "target-1",
@@ -27,17 +102,19 @@ function setupMocks(body, status = 200) {
27
102
  },
28
103
  ]);
29
104
  const disconnect = vi.fn();
105
+ const navigate = vi.fn().mockResolvedValue({ frameId: "F1" });
106
+ const send = vi.fn().mockResolvedValue(undefined);
107
+ const evaluate = createEvaluateMock(scrapedPosts);
30
108
  vi.mocked(CDPClient).mockImplementation(function () {
31
109
  return {
32
110
  connect: vi.fn().mockResolvedValue(undefined),
33
111
  disconnect,
112
+ navigate,
113
+ evaluate,
114
+ send,
34
115
  };
35
116
  });
36
- const fetchMock = vi.fn().mockResolvedValue({ url: "", status, body });
37
- vi.mocked(VoyagerInterceptor).mockImplementation(function () {
38
- return { fetch: fetchMock };
39
- });
40
- return { fetchMock, disconnect };
117
+ return { navigate, disconnect, evaluate, send };
41
118
  }
42
119
  describe("getFeed", () => {
43
120
  beforeEach(() => {
@@ -46,178 +123,265 @@ describe("getFeed", () => {
46
123
  afterEach(() => {
47
124
  vi.restoreAllMocks();
48
125
  });
49
- it("parses feed elements with inline actor and social detail", async () => {
50
- const { fetchMock } = setupMocks({
51
- data: {
52
- elements: [
53
- {
54
- updateUrn: "urn:li:activity:123",
55
- actor: {
56
- name: { text: "Alice Smith" },
57
- description: { text: "Engineer at Acme" },
58
- navigationUrl: "https://www.linkedin.com/in/alice/",
59
- },
60
- commentary: { text: { text: "Hello #linkedin #tech world!" } },
61
- content: { mediaCategory: "IMAGE" },
62
- socialDetail: {
63
- totalSocialActivityCounts: {
64
- numLikes: 10,
65
- numComments: 3,
66
- numShares: 1,
67
- },
68
- },
69
- createdAt: 1700000000000,
70
- },
71
- ],
72
- metadata: { paginationToken: "cursor-abc" },
73
- },
74
- });
126
+ it("parses posts from DOM-scraped data", async () => {
127
+ setupMocks([
128
+ rawPost({
129
+ url: "https://www.linkedin.com/feed/update/urn:li:activity:123/",
130
+ authorName: "Alice Smith",
131
+ authorHeadline: "Engineer at Acme",
132
+ authorProfileUrl: "https://www.linkedin.com/in/alice",
133
+ text: "Hello #linkedin #tech world!",
134
+ mediaType: "image",
135
+ reactionCount: 42,
136
+ commentCount: 7,
137
+ shareCount: 3,
138
+ timestamp: "2h",
139
+ }),
140
+ ]);
75
141
  const result = await getFeed({ cdpPort: CDP_PORT });
76
142
  expect(result.posts).toHaveLength(1);
77
143
  const [post] = result.posts;
78
- expect(post?.urn).toBe("urn:li:activity:123");
79
144
  expect(post?.url).toBe("https://www.linkedin.com/feed/update/urn:li:activity:123/");
80
145
  expect(post?.authorName).toBe("Alice Smith");
81
146
  expect(post?.authorHeadline).toBe("Engineer at Acme");
82
- expect(post?.authorProfileUrl).toBe("https://www.linkedin.com/in/alice/");
147
+ expect(post?.authorProfileUrl).toBe("https://www.linkedin.com/in/alice");
83
148
  expect(post?.text).toBe("Hello #linkedin #tech world!");
84
149
  expect(post?.mediaType).toBe("image");
85
- expect(post?.reactionCount).toBe(10);
86
- expect(post?.commentCount).toBe(3);
87
- expect(post?.shareCount).toBe(1);
88
- expect(post?.timestamp).toBe(1700000000000);
150
+ expect(post?.reactionCount).toBe(42);
151
+ expect(post?.commentCount).toBe(7);
152
+ expect(post?.shareCount).toBe(3);
89
153
  expect(post?.hashtags).toEqual(["linkedin", "tech"]);
90
- expect(result.nextCursor).toBe("cursor-abc");
91
- expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining("/voyager/api/feed/dash/feedUpdates"));
92
- });
93
- it("resolves actor from included entities via *actor reference", async () => {
94
- setupMocks({
95
- elements: [
96
- {
97
- updateUrn: "urn:li:activity:456",
98
- "*actor": "urn:li:fsd_profile:789",
99
- commentary: { text: "Post text" },
100
- },
101
- ],
102
- included: [
103
- {
104
- entityUrn: "urn:li:fsd_profile:789",
105
- firstName: "Bob",
106
- lastName: "Jones",
107
- publicIdentifier: "bobjones",
108
- headline: { text: "CEO at Corp" },
109
- },
110
- ],
111
- });
154
+ });
155
+ it("returns null URL when raw post has null url", async () => {
156
+ setupMocks([rawPost({ url: null })]);
112
157
  const result = await getFeed({ cdpPort: CDP_PORT });
113
- expect(result.posts).toHaveLength(1);
114
- const [post] = result.posts;
115
- expect(post?.authorName).toBe("Bob Jones");
116
- expect(post?.authorHeadline).toBe("CEO at Corp");
117
- expect(post?.authorProfileUrl).toBe("https://www.linkedin.com/in/bobjones/");
118
- expect(result.nextCursor).toBeNull();
158
+ expect(result.posts[0]?.url).toBeNull();
119
159
  });
120
- it("resolves social detail from included entities via *socialDetail", async () => {
121
- setupMocks({
122
- elements: [
123
- {
124
- updateUrn: "urn:li:activity:789",
125
- "*socialDetail": "urn:li:fsd_socialDetail:789",
126
- },
127
- ],
128
- included: [
129
- {
130
- entityUrn: "urn:li:fsd_socialDetail:789",
131
- totalSocialActivityCounts: {
132
- numLikes: 50,
133
- numComments: 20,
134
- numShares: 5,
135
- },
136
- },
137
- ],
160
+ it("retries URL extraction when clipboard capture fails on first attempt", async () => {
161
+ const retryUrl = "https://www.linkedin.com/feed/update/urn:li:activity:retried/";
162
+ // Post scraped without a URL — URL extraction must fill it in
163
+ const post = rawPost({ url: null });
164
+ vi.mocked(discoverTargets).mockResolvedValue([
165
+ {
166
+ id: "target-1",
167
+ type: "page",
168
+ title: "LinkedIn",
169
+ url: "https://www.linkedin.com/feed/",
170
+ description: "",
171
+ devtoolsFrontendUrl: "",
172
+ },
173
+ ]);
174
+ let clipboardReadCount = 0;
175
+ const evaluate = vi.fn().mockImplementation((script) => {
176
+ const s = String(script);
177
+ if (s.includes("parseCount"))
178
+ return Promise.resolve([post]);
179
+ if (s.includes("navigator.clipboard.writeText"))
180
+ return Promise.resolve(undefined);
181
+ if (s.includes("__capturedClipboard = null"))
182
+ return Promise.resolve(undefined);
183
+ if (s.includes("Copy link to post"))
184
+ return Promise.resolve(undefined);
185
+ if (s === "window.__capturedClipboard") {
186
+ clipboardReadCount++;
187
+ return Promise.resolve(clipboardReadCount === 1 ? null : retryUrl);
188
+ }
189
+ if (s.includes("btn.click()"))
190
+ return Promise.resolve(true);
191
+ if (s.includes("scrollIntoView"))
192
+ return Promise.resolve(undefined);
193
+ if (s.includes("mainFeed"))
194
+ return Promise.resolve(true);
195
+ return Promise.resolve(undefined);
196
+ });
197
+ vi.mocked(CDPClient).mockImplementation(function () {
198
+ return {
199
+ connect: vi.fn().mockResolvedValue(undefined),
200
+ disconnect: vi.fn(),
201
+ navigate: vi.fn().mockResolvedValue({ frameId: "F1" }),
202
+ evaluate,
203
+ send: vi.fn().mockResolvedValue(undefined),
204
+ };
138
205
  });
139
206
  const result = await getFeed({ cdpPort: CDP_PORT });
140
- const [post] = result.posts;
141
- expect(post?.reactionCount).toBe(50);
142
- expect(post?.commentCount).toBe(20);
143
- expect(post?.shareCount).toBe(5);
144
- });
145
- it("passes cursor as paginationToken query parameter", async () => {
146
- const { fetchMock } = setupMocks({ elements: [] });
147
- await getFeed({ cdpPort: CDP_PORT, cursor: "my-cursor-token" });
148
- expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining("paginationToken=my-cursor-token"));
149
- });
150
- it("passes count query parameter", async () => {
151
- const { fetchMock } = setupMocks({ elements: [] });
152
- await getFeed({ cdpPort: CDP_PORT, count: 5 });
153
- expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining("count=5"));
154
- });
155
- it("defaults to count=10", async () => {
156
- const { fetchMock } = setupMocks({ elements: [] });
207
+ expect(result.posts[0]?.url).toBe(retryUrl);
208
+ expect(clipboardReadCount).toBeGreaterThanOrEqual(2);
209
+ });
210
+ it("navigates to the LinkedIn feed page", async () => {
211
+ const { navigate } = setupMocks([rawPost()]);
157
212
  await getFeed({ cdpPort: CDP_PORT });
158
- expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining("count=10"));
159
- });
160
- it("skips elements without URN", async () => {
161
- setupMocks({
162
- elements: [
163
- { commentary: { text: "no urn" } },
164
- { updateUrn: "urn:li:activity:999" },
165
- ],
166
- });
213
+ expect(navigate).toHaveBeenCalledWith("https://www.linkedin.com/feed/");
214
+ });
215
+ it("returns null authorPublicId for all posts", async () => {
216
+ setupMocks([rawPost()]);
167
217
  const result = await getFeed({ cdpPort: CDP_PORT });
168
- expect(result.posts).toHaveLength(1);
169
- expect(result.posts[0]?.urn).toBe("urn:li:activity:999");
218
+ expect(result.posts[0]?.authorPublicId).toBeNull();
170
219
  });
171
- it("extracts and deduplicates hashtags from post text", async () => {
172
- setupMocks({
173
- elements: [
174
- {
175
- updateUrn: "urn:li:activity:100",
176
- commentary: { text: { text: "#AI and #MachineLearning are #AI transforming" } },
177
- },
178
- ],
220
+ it("limits results to count parameter", async () => {
221
+ setupMocks([
222
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:1/" }),
223
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:2/" }),
224
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:3/" }),
225
+ ]);
226
+ const result = await getFeed({ cdpPort: CDP_PORT, count: 2 });
227
+ expect(result.posts).toHaveLength(2);
228
+ expect(result.posts[0]?.url).toBe("https://www.linkedin.com/feed/update/urn:li:activity:1/");
229
+ expect(result.posts[1]?.url).toBe("https://www.linkedin.com/feed/update/urn:li:activity:2/");
230
+ });
231
+ it("returns nextCursor when more posts are available", async () => {
232
+ setupMocks([
233
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:1/" }),
234
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:2/" }),
235
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:3/" }),
236
+ ]);
237
+ const result = await getFeed({ cdpPort: CDP_PORT, count: 2 });
238
+ expect(result.nextCursor).toBe("https://www.linkedin.com/feed/update/urn:li:activity:2/");
239
+ });
240
+ it("returns null nextCursor when all posts are returned", async () => {
241
+ setupMocks([
242
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:1/" }),
243
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:2/" }),
244
+ ]);
245
+ const result = await getFeed({ cdpPort: CDP_PORT, count: 10 });
246
+ expect(result.nextCursor).toBeNull();
247
+ });
248
+ it("supports cursor-based pagination", { timeout: 15_000 }, async () => {
249
+ setupMocks([
250
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:1/" }),
251
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:2/" }),
252
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:3/" }),
253
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:4/" }),
254
+ ]);
255
+ const result = await getFeed({
256
+ cdpPort: CDP_PORT,
257
+ count: 2,
258
+ cursor: "https://www.linkedin.com/feed/update/urn:li:activity:2/",
179
259
  });
180
- const result = await getFeed({ cdpPort: CDP_PORT });
181
- expect(result.posts[0]?.hashtags).toEqual(["AI", "MachineLearning"]);
260
+ expect(result.posts).toHaveLength(2);
261
+ expect(result.posts[0]?.url).toBe("https://www.linkedin.com/feed/update/urn:li:activity:3/");
262
+ expect(result.posts[1]?.url).toBe("https://www.linkedin.com/feed/update/urn:li:activity:4/");
182
263
  });
183
- it("returns empty hashtags when no text", async () => {
184
- setupMocks({
185
- elements: [{ updateUrn: "urn:li:activity:101" }],
264
+ it("returns empty posts when cursor is at the end", async () => {
265
+ setupMocks([
266
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:1/" }),
267
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:2/" }),
268
+ ]);
269
+ const result = await getFeed({
270
+ cdpPort: CDP_PORT,
271
+ cursor: "https://www.linkedin.com/feed/update/urn:li:activity:2/",
186
272
  });
187
- const result = await getFeed({ cdpPort: CDP_PORT });
188
- expect(result.posts[0]?.hashtags).toEqual([]);
273
+ expect(result.posts).toHaveLength(0);
274
+ expect(result.nextCursor).toBeNull();
189
275
  });
190
- it("infers media type from $type when mediaCategory absent", async () => {
191
- setupMocks({
192
- elements: [
193
- {
194
- updateUrn: "urn:li:activity:200",
195
- content: { $type: "com.linkedin.voyager.feed.render.VideoComponent" },
196
- },
197
- ],
276
+ it("treats unknown cursor as start of feed", async () => {
277
+ setupMocks([
278
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:1/" }),
279
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:2/" }),
280
+ ]);
281
+ const result = await getFeed({
282
+ cdpPort: CDP_PORT,
283
+ cursor: "https://www.linkedin.com/feed/update/urn:li:activity:unknown/",
198
284
  });
285
+ // When cursor is not found, all posts are returned from the start
286
+ expect(result.posts).toHaveLength(2);
287
+ });
288
+ it("handles empty feed", async () => {
289
+ setupMocks([]);
199
290
  const result = await getFeed({ cdpPort: CDP_PORT });
200
- expect(result.posts[0]?.mediaType).toBe("video");
201
- });
202
- it("infers article from navigationUrl", async () => {
203
- setupMocks({
204
- elements: [
205
- {
206
- updateUrn: "urn:li:activity:201",
207
- content: { navigationUrl: "https://example.com/article" },
208
- },
209
- ],
210
- });
291
+ expect(result.posts).toHaveLength(0);
292
+ expect(result.nextCursor).toBeNull();
293
+ });
294
+ it("handles posts with null fields", async () => {
295
+ setupMocks([rawPost()]);
211
296
  const result = await getFeed({ cdpPort: CDP_PORT });
212
- expect(result.posts[0]?.mediaType).toBe("article");
297
+ expect(result.posts[0]?.authorName).toBeNull();
298
+ expect(result.posts[0]?.authorHeadline).toBeNull();
299
+ expect(result.posts[0]?.authorProfileUrl).toBeNull();
300
+ expect(result.posts[0]?.text).toBeNull();
301
+ expect(result.posts[0]?.mediaType).toBeNull();
302
+ expect(result.posts[0]?.timestamp).toBeNull();
213
303
  });
214
- it("throws on non-200 response", async () => {
215
- setupMocks(null, 403);
216
- await expect(getFeed({ cdpPort: CDP_PORT })).rejects.toThrow("Voyager API returned HTTP 403 for feed");
304
+ it("scrolls to load more posts when count exceeds initial scrape", { timeout: 15_000 }, async () => {
305
+ const { evaluate, send } = setupMocks([]);
306
+ let scrapeCall = 0;
307
+ const firstScrape = [
308
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:1/" }),
309
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:2/" }),
310
+ ];
311
+ const secondScrape = [
312
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:1/" }),
313
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:2/" }),
314
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:3/" }),
315
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:4/" }),
316
+ ];
317
+ let urnIdx = 0;
318
+ evaluate.mockReset();
319
+ evaluate.mockImplementation((script) => {
320
+ const s = String(script);
321
+ if (s.includes("parseCount")) {
322
+ scrapeCall++;
323
+ return Promise.resolve(scrapeCall === 1 ? firstScrape : secondScrape);
324
+ }
325
+ if (s.includes("embed-modal")) {
326
+ urnIdx++;
327
+ return Promise.resolve(`urn:li:activity:${String(urnIdx)}`);
328
+ }
329
+ if (s.includes("btn.click()")) {
330
+ return Promise.resolve(true);
331
+ }
332
+ if (s.includes("scrollIntoView")) {
333
+ return Promise.resolve(undefined);
334
+ }
335
+ if (s.includes("mainFeed")) {
336
+ return Promise.resolve(true);
337
+ }
338
+ return Promise.resolve(null);
339
+ });
340
+ const result = await getFeed({ cdpPort: CDP_PORT, count: 4 });
341
+ expect(result.posts).toHaveLength(4);
342
+ // scrollFeed uses gaussianBetween (mocked to return 800) for distance, x, and y
343
+ expect(send).toHaveBeenCalledWith("Input.dispatchMouseEvent", {
344
+ type: "mouseWheel",
345
+ x: 800,
346
+ y: 800,
347
+ deltaX: 0,
348
+ deltaY: 800,
349
+ });
217
350
  });
218
- it("throws on non-object response body", async () => {
219
- setupMocks(null, 200);
220
- await expect(getFeed({ cdpPort: CDP_PORT })).rejects.toThrow("Voyager API returned an unexpected response format for feed");
351
+ it("stops scrolling when no new posts appear", async () => {
352
+ const { evaluate, send } = setupMocks([]);
353
+ const fixedPosts = [
354
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:1/" }),
355
+ rawPost({ url: "https://www.linkedin.com/feed/update/urn:li:activity:2/" }),
356
+ ];
357
+ let urnIdx = 0;
358
+ evaluate.mockReset();
359
+ evaluate.mockImplementation((script) => {
360
+ const s = String(script);
361
+ if (s.includes("parseCount")) {
362
+ return Promise.resolve(fixedPosts);
363
+ }
364
+ if (s.includes("embed-modal")) {
365
+ urnIdx++;
366
+ return Promise.resolve(`urn:li:activity:${String(urnIdx)}`);
367
+ }
368
+ if (s.includes("btn.click()")) {
369
+ return Promise.resolve(true);
370
+ }
371
+ if (s.includes("scrollIntoView")) {
372
+ return Promise.resolve(undefined);
373
+ }
374
+ if (s.includes("mainFeed")) {
375
+ return Promise.resolve(true);
376
+ }
377
+ return Promise.resolve(null);
378
+ });
379
+ const result = await getFeed({ cdpPort: CDP_PORT, count: 10 });
380
+ expect(result.posts).toHaveLength(2);
381
+ // At least 1 scroll from the feed loop; confusion scrolls from retry
382
+ // logic may add more Input.dispatchMouseEvent calls.
383
+ const scrollCalls = send.mock.calls.filter((c) => c[0] === "Input.dispatchMouseEvent");
384
+ expect(scrollCalls.length).toBeGreaterThanOrEqual(1);
221
385
  });
222
386
  it("throws when no LinkedIn page found", async () => {
223
387
  vi.mocked(discoverTargets).mockResolvedValue([]);
@@ -227,50 +391,112 @@ describe("getFeed", () => {
227
391
  await expect(getFeed({ cdpPort: CDP_PORT, cdpHost: "192.168.1.1" })).rejects.toThrow("requires --allow-remote");
228
392
  });
229
393
  it("disconnects CDP client after operation", async () => {
230
- const { disconnect } = setupMocks({ elements: [] });
394
+ const { disconnect } = setupMocks([rawPost()]);
231
395
  await getFeed({ cdpPort: CDP_PORT });
232
396
  expect(disconnect).toHaveBeenCalled();
233
397
  });
234
398
  it("disconnects CDP client even on error", async () => {
235
- const { disconnect } = setupMocks(null, 500);
236
- await expect(getFeed({ cdpPort: CDP_PORT })).rejects.toThrow();
399
+ vi.mocked(discoverTargets).mockResolvedValue([
400
+ {
401
+ id: "target-1",
402
+ type: "page",
403
+ title: "LinkedIn",
404
+ url: "https://www.linkedin.com/feed/",
405
+ description: "",
406
+ devtoolsFrontendUrl: "",
407
+ },
408
+ ]);
409
+ const disconnect = vi.fn();
410
+ vi.mocked(CDPClient).mockImplementation(function () {
411
+ return {
412
+ connect: vi.fn().mockResolvedValue(undefined),
413
+ disconnect,
414
+ navigate: vi.fn().mockRejectedValue(new Error("nav error")),
415
+ evaluate: vi.fn(),
416
+ send: vi.fn(),
417
+ };
418
+ });
419
+ await expect(getFeed({ cdpPort: CDP_PORT })).rejects.toThrow("nav error");
237
420
  expect(disconnect).toHaveBeenCalled();
238
421
  });
239
- it("handles company actor from included entities", async () => {
240
- setupMocks({
241
- elements: [
242
- {
243
- updateUrn: "urn:li:activity:300",
244
- "*actor": "urn:li:fsd_company:100",
245
- },
246
- ],
247
- included: [
248
- {
249
- entityUrn: "urn:li:fsd_company:100",
250
- name: { text: "Acme Corp" },
251
- description: { text: "Technology company" },
252
- navigationUrl: "https://www.linkedin.com/company/acme/",
253
- },
254
- ],
255
- });
422
+ it("parses relative timestamp into epoch milliseconds", async () => {
423
+ const now = Date.now();
424
+ setupMocks([rawPost({ timestamp: "2h" })]);
256
425
  const result = await getFeed({ cdpPort: CDP_PORT });
257
- expect(result.posts[0]?.authorName).toBe("Acme Corp");
258
- expect(result.posts[0]?.authorHeadline).toBe("Technology company");
259
- expect(result.posts[0]?.authorProfileUrl).toBe("https://www.linkedin.com/company/acme/");
426
+ const ts = result.posts[0]?.timestamp;
427
+ expect(ts).toBeTypeOf("number");
428
+ // Should be approximately 2 hours ago (within 5 seconds tolerance)
429
+ const twoHoursMs = 2 * 60 * 60 * 1000;
430
+ expect(Math.abs((now - twoHoursMs) - ts)).toBeLessThan(5000);
260
431
  });
261
- it("uses urn field when updateUrn absent", async () => {
262
- setupMocks({
263
- elements: [{ urn: "urn:li:activity:400" }],
264
- });
432
+ it("extracts and deduplicates hashtags from post text", async () => {
433
+ setupMocks([
434
+ rawPost({
435
+ text: "#AI and #MachineLearning are #AI transforming",
436
+ }),
437
+ ]);
265
438
  const result = await getFeed({ cdpPort: CDP_PORT });
266
- expect(result.posts[0]?.urn).toBe("urn:li:activity:400");
439
+ expect(result.posts[0]?.hashtags).toEqual(["AI", "MachineLearning"]);
267
440
  });
268
- it("uses publishedAt as fallback timestamp", async () => {
269
- setupMocks({
270
- elements: [{ updateUrn: "urn:li:activity:500", publishedAt: 1600000000000 }],
271
- });
441
+ it("returns empty hashtags when no text", async () => {
442
+ setupMocks([rawPost()]);
272
443
  const result = await getFeed({ cdpPort: CDP_PORT });
273
- expect(result.posts[0]?.timestamp).toBe(1600000000000);
444
+ expect(result.posts[0]?.hashtags).toEqual([]);
445
+ });
446
+ });
447
+ describe("extractHashtags", () => {
448
+ it("extracts unique hashtags", () => {
449
+ expect(extractHashtags("#hello #world #hello")).toEqual(["hello", "world"]);
450
+ });
451
+ it("handles accented characters", () => {
452
+ expect(extractHashtags("#café #résumé")).toEqual(["café", "résumé"]);
453
+ });
454
+ it("returns empty array for null text", () => {
455
+ expect(extractHashtags(null)).toEqual([]);
456
+ });
457
+ it("returns empty array when no hashtags", () => {
458
+ expect(extractHashtags("no hashtags here")).toEqual([]);
459
+ });
460
+ });
461
+ describe("parseTimestamp", () => {
462
+ it("parses seconds", () => {
463
+ const now = Date.now();
464
+ const result = parseTimestamp("30s");
465
+ expect(result).toBeTypeOf("number");
466
+ expect(Math.abs((now - 30_000) - result)).toBeLessThan(1000);
467
+ });
468
+ it("parses minutes", () => {
469
+ const now = Date.now();
470
+ const result = parseTimestamp("52m");
471
+ expect(result).toBeTypeOf("number");
472
+ expect(Math.abs((now - 52 * 60_000) - result)).toBeLessThan(1000);
473
+ });
474
+ it("parses hours", () => {
475
+ const now = Date.now();
476
+ const result = parseTimestamp("16h");
477
+ expect(result).toBeTypeOf("number");
478
+ expect(Math.abs((now - 16 * 3_600_000) - result)).toBeLessThan(1000);
479
+ });
480
+ it("parses days", () => {
481
+ const now = Date.now();
482
+ const result = parseTimestamp("3d");
483
+ expect(result).toBeTypeOf("number");
484
+ expect(Math.abs((now - 3 * 86_400_000) - result)).toBeLessThan(1000);
485
+ });
486
+ it("parses weeks", () => {
487
+ const now = Date.now();
488
+ const result = parseTimestamp("1w");
489
+ expect(result).toBeTypeOf("number");
490
+ expect(Math.abs((now - 604_800_000) - result)).toBeLessThan(1000);
491
+ });
492
+ it("parses ISO datetime", () => {
493
+ expect(parseTimestamp("2026-03-25T10:00:00Z")).toBe(Date.parse("2026-03-25T10:00:00Z"));
494
+ });
495
+ it("returns null for null input", () => {
496
+ expect(parseTimestamp(null)).toBeNull();
497
+ });
498
+ it("returns null for unrecognised format", () => {
499
+ expect(parseTimestamp("unknown")).toBeNull();
274
500
  });
275
501
  });
276
502
  //# sourceMappingURL=get-feed.test.js.map