@rudderhq/server 0.6.6-canary.8 → 0.7.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 (426) hide show
  1. package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
  2. package/dist/bootstrap/register-api-routes.js +6 -0
  3. package/dist/bootstrap/register-api-routes.js.map +1 -1
  4. package/dist/bundled-plugins/plugin-linear/dist/worker.js +152 -9
  5. package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +3 -3
  6. package/dist/routes/access.d.ts.map +1 -1
  7. package/dist/routes/access.js +6 -0
  8. package/dist/routes/access.js.map +1 -1
  9. package/dist/routes/agents.management-routes.d.ts.map +1 -1
  10. package/dist/routes/agents.management-routes.js +22 -0
  11. package/dist/routes/agents.management-routes.js.map +1 -1
  12. package/dist/routes/ai-search.d.ts +3 -0
  13. package/dist/routes/ai-search.d.ts.map +1 -0
  14. package/dist/routes/ai-search.js +17 -0
  15. package/dist/routes/ai-search.js.map +1 -0
  16. package/dist/routes/app-builder.d.ts +3 -0
  17. package/dist/routes/app-builder.d.ts.map +1 -0
  18. package/dist/routes/app-builder.js +305 -0
  19. package/dist/routes/app-builder.js.map +1 -0
  20. package/dist/routes/browser.d.ts.map +1 -1
  21. package/dist/routes/browser.js +6 -2
  22. package/dist/routes/browser.js.map +1 -1
  23. package/dist/routes/chats.annotation-routes.d.ts.map +1 -1
  24. package/dist/routes/chats.annotation-routes.js +10 -1
  25. package/dist/routes/chats.annotation-routes.js.map +1 -1
  26. package/dist/routes/chats.d.ts.map +1 -1
  27. package/dist/routes/chats.js +1 -0
  28. package/dist/routes/chats.js.map +1 -1
  29. package/dist/routes/chats.runtime-controls.d.ts +1 -0
  30. package/dist/routes/chats.runtime-controls.d.ts.map +1 -1
  31. package/dist/routes/chats.stream-routes.d.ts.map +1 -1
  32. package/dist/routes/chats.stream-routes.js +52 -4
  33. package/dist/routes/chats.stream-routes.js.map +1 -1
  34. package/dist/routes/chats.stream-support.d.ts +1 -0
  35. package/dist/routes/chats.stream-support.d.ts.map +1 -1
  36. package/dist/routes/chats.stream-support.js +8 -0
  37. package/dist/routes/chats.stream-support.js.map +1 -1
  38. package/dist/routes/health.d.ts.map +1 -1
  39. package/dist/routes/health.js +1 -0
  40. package/dist/routes/health.js.map +1 -1
  41. package/dist/routes/index.d.ts +2 -0
  42. package/dist/routes/index.d.ts.map +1 -1
  43. package/dist/routes/index.js +2 -0
  44. package/dist/routes/index.js.map +1 -1
  45. package/dist/routes/issues.mutations.d.ts +1 -1
  46. package/dist/routes/issues.mutations.d.ts.map +1 -1
  47. package/dist/routes/issues.mutations.js +71 -7
  48. package/dist/routes/issues.mutations.js.map +1 -1
  49. package/dist/routes/onboarding.d.ts.map +1 -1
  50. package/dist/routes/onboarding.js +35 -32
  51. package/dist/routes/onboarding.js.map +1 -1
  52. package/dist/routes/orgs.js +1 -1
  53. package/dist/routes/orgs.js.map +1 -1
  54. package/dist/routes/product-analytics-collector.d.ts +10 -0
  55. package/dist/routes/product-analytics-collector.d.ts.map +1 -0
  56. package/dist/routes/product-analytics-collector.js +28 -0
  57. package/dist/routes/product-analytics-collector.js.map +1 -0
  58. package/dist/routes/product-analytics.d.ts +3 -0
  59. package/dist/routes/product-analytics.d.ts.map +1 -0
  60. package/dist/routes/product-analytics.js +169 -0
  61. package/dist/routes/product-analytics.js.map +1 -0
  62. package/dist/routes/website-metadata.d.ts +2 -2
  63. package/dist/routes/website-metadata.d.ts.map +1 -1
  64. package/dist/routes/website-metadata.js +15 -2
  65. package/dist/routes/website-metadata.js.map +1 -1
  66. package/dist/services/activity.d.ts +3 -1
  67. package/dist/services/activity.d.ts.map +1 -1
  68. package/dist/services/activity.js +55 -6
  69. package/dist/services/activity.js.map +1 -1
  70. package/dist/services/agent-run-context.d.ts.map +1 -1
  71. package/dist/services/agent-run-context.js +8 -2
  72. package/dist/services/agent-run-context.js.map +1 -1
  73. package/dist/services/agents.d.ts +84 -13
  74. package/dist/services/agents.d.ts.map +1 -1
  75. package/dist/services/agents.js +35 -1
  76. package/dist/services/agents.js.map +1 -1
  77. package/dist/services/ai-search.d.ts +6 -0
  78. package/dist/services/ai-search.d.ts.map +1 -0
  79. package/dist/services/ai-search.js +238 -0
  80. package/dist/services/ai-search.js.map +1 -0
  81. package/dist/services/ai-search.test.d.ts +2 -0
  82. package/dist/services/ai-search.test.d.ts.map +1 -0
  83. package/dist/services/ai-search.test.js +52 -0
  84. package/dist/services/ai-search.test.js.map +1 -0
  85. package/dist/services/app-builder.d.ts +13 -0
  86. package/dist/services/app-builder.d.ts.map +1 -0
  87. package/dist/services/app-builder.js +241 -0
  88. package/dist/services/app-builder.js.map +1 -0
  89. package/dist/services/approvals.d.ts +3 -3
  90. package/dist/services/automations.d.ts +1 -0
  91. package/dist/services/automations.d.ts.map +1 -1
  92. package/dist/services/automations.js +33 -1
  93. package/dist/services/automations.js.map +1 -1
  94. package/dist/services/browser-broker.d.ts +1 -0
  95. package/dist/services/browser-broker.d.ts.map +1 -1
  96. package/dist/services/browser-broker.js +34 -2
  97. package/dist/services/browser-broker.js.map +1 -1
  98. package/dist/services/browser-broker.test.js +73 -0
  99. package/dist/services/browser-broker.test.js.map +1 -1
  100. package/dist/services/budgets.d.ts +1 -6
  101. package/dist/services/budgets.d.ts.map +1 -1
  102. package/dist/services/budgets.js +30 -103
  103. package/dist/services/budgets.js.map +1 -1
  104. package/dist/services/chat-annotation-copy-lineage.d.ts.map +1 -1
  105. package/dist/services/chat-annotation-copy-lineage.js +6 -1
  106. package/dist/services/chat-annotation-copy-lineage.js.map +1 -1
  107. package/dist/services/chat-assistant.annotations.d.ts.map +1 -1
  108. package/dist/services/chat-assistant.annotations.js +4 -1
  109. package/dist/services/chat-assistant.annotations.js.map +1 -1
  110. package/dist/services/chat-assistant.annotations.test.js +18 -2
  111. package/dist/services/chat-assistant.annotations.test.js.map +1 -1
  112. package/dist/services/chat-family-groups.d.ts +1 -1
  113. package/dist/services/chat-family-groups.d.ts.map +1 -1
  114. package/dist/services/chat-family-groups.js +17 -1
  115. package/dist/services/chat-family-groups.js.map +1 -1
  116. package/dist/services/chat-inline-annotation-validation.d.ts.map +1 -1
  117. package/dist/services/chat-inline-annotation-validation.js +301 -8
  118. package/dist/services/chat-inline-annotation-validation.js.map +1 -1
  119. package/dist/services/chat-inline-annotations.test.js +249 -1
  120. package/dist/services/chat-inline-annotations.test.js.map +1 -1
  121. package/dist/services/chat-message-branch.d.ts +5 -0
  122. package/dist/services/chat-message-branch.d.ts.map +1 -0
  123. package/dist/services/chat-message-branch.js +9 -0
  124. package/dist/services/chat-message-branch.js.map +1 -0
  125. package/dist/services/chat-work-manifest.d.ts.map +1 -1
  126. package/dist/services/chat-work-manifest.js +6 -0
  127. package/dist/services/chat-work-manifest.js.map +1 -1
  128. package/dist/services/chats.create.d.ts +2 -0
  129. package/dist/services/chats.create.d.ts.map +1 -1
  130. package/dist/services/chats.create.js +76 -4
  131. package/dist/services/chats.create.js.map +1 -1
  132. package/dist/services/chats.d.ts +33 -13
  133. package/dist/services/chats.d.ts.map +1 -1
  134. package/dist/services/chats.inline-visual-persistence.d.ts.map +1 -1
  135. package/dist/services/chats.inline-visual-persistence.js +7 -0
  136. package/dist/services/chats.inline-visual-persistence.js.map +1 -1
  137. package/dist/services/chats.js +46 -24
  138. package/dist/services/chats.js.map +1 -1
  139. package/dist/services/dashboard.d.ts +0 -1
  140. package/dist/services/dashboard.d.ts.map +1 -1
  141. package/dist/services/dashboard.js +0 -1
  142. package/dist/services/dashboard.js.map +1 -1
  143. package/dist/services/heartbeat-run-summary.d.ts.map +1 -1
  144. package/dist/services/heartbeat-run-summary.js +6 -0
  145. package/dist/services/heartbeat-run-summary.js.map +1 -1
  146. package/dist/services/index.d.ts +2 -0
  147. package/dist/services/index.d.ts.map +1 -1
  148. package/dist/services/index.js +2 -0
  149. package/dist/services/index.js.map +1 -1
  150. package/dist/services/instance-settings.d.ts.map +1 -1
  151. package/dist/services/instance-settings.js +4 -6
  152. package/dist/services/instance-settings.js.map +1 -1
  153. package/dist/services/issue-approvals.d.ts +1 -1
  154. package/dist/services/issues.d.ts +2 -2
  155. package/dist/services/issues.d.ts.map +1 -1
  156. package/dist/services/issues.js +69 -2
  157. package/dist/services/issues.js.map +1 -1
  158. package/dist/services/knowledge-portability/organization-portability.import.d.ts.map +1 -1
  159. package/dist/services/knowledge-portability/organization-portability.import.js +1 -0
  160. package/dist/services/knowledge-portability/organization-portability.import.js.map +1 -1
  161. package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
  162. package/dist/services/knowledge-portability/organization-skills.js +6 -2
  163. package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
  164. package/dist/services/legacy-operator-state.d.ts +14 -0
  165. package/dist/services/legacy-operator-state.d.ts.map +1 -0
  166. package/dist/services/legacy-operator-state.js +251 -0
  167. package/dist/services/legacy-operator-state.js.map +1 -0
  168. package/dist/services/local-account-auth.d.ts.map +1 -1
  169. package/dist/services/local-account-auth.js +21 -3
  170. package/dist/services/local-account-auth.js.map +1 -1
  171. package/dist/services/messenger-custom-groups.d.ts +15 -0
  172. package/dist/services/messenger-custom-groups.d.ts.map +1 -0
  173. package/dist/services/messenger-custom-groups.js +155 -0
  174. package/dist/services/messenger-custom-groups.js.map +1 -0
  175. package/dist/services/messenger-saved-views.d.ts +32 -8
  176. package/dist/services/messenger-saved-views.d.ts.map +1 -1
  177. package/dist/services/messenger-saved-views.js +67 -2
  178. package/dist/services/messenger-saved-views.js.map +1 -1
  179. package/dist/services/messenger.d.ts +3 -3
  180. package/dist/services/messenger.d.ts.map +1 -1
  181. package/dist/services/messenger.js +110 -166
  182. package/dist/services/messenger.js.map +1 -1
  183. package/dist/services/orgs.d.ts +11 -2
  184. package/dist/services/orgs.d.ts.map +1 -1
  185. package/dist/services/orgs.js +23 -1
  186. package/dist/services/orgs.js.map +1 -1
  187. package/dist/services/product-analytics-collector.d.ts +86 -0
  188. package/dist/services/product-analytics-collector.d.ts.map +1 -0
  189. package/dist/services/product-analytics-collector.js +172 -0
  190. package/dist/services/product-analytics-collector.js.map +1 -0
  191. package/dist/services/product-analytics-collector.test.d.ts +2 -0
  192. package/dist/services/product-analytics-collector.test.d.ts.map +1 -0
  193. package/dist/services/product-analytics-collector.test.js +81 -0
  194. package/dist/services/product-analytics-collector.test.js.map +1 -0
  195. package/dist/services/product-analytics.d.ts +385 -0
  196. package/dist/services/product-analytics.d.ts.map +1 -0
  197. package/dist/services/product-analytics.js +698 -0
  198. package/dist/services/product-analytics.js.map +1 -0
  199. package/dist/services/product-analytics.test.d.ts +2 -0
  200. package/dist/services/product-analytics.test.d.ts.map +1 -0
  201. package/dist/services/product-analytics.test.js +136 -0
  202. package/dist/services/product-analytics.test.js.map +1 -0
  203. package/dist/services/projects.d.ts +1 -1
  204. package/dist/services/runtime-kernel/heartbeat.d.ts +4 -0
  205. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  206. package/dist/services/runtime-kernel/heartbeat.js +53 -1
  207. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  208. package/dist/services/runtime-kernel/heartbeat.misc.d.ts.map +1 -1
  209. package/dist/services/runtime-kernel/heartbeat.misc.js +5 -10
  210. package/dist/services/runtime-kernel/heartbeat.misc.js.map +1 -1
  211. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts +3 -0
  212. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -1
  213. package/dist/services/runtime-kernel/heartbeat.terminal.js +56 -1
  214. package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -1
  215. package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
  216. package/dist/services/runtime-kernel/heartbeat.wakeup.js +9 -2
  217. package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
  218. package/dist/services/secrets.d.ts +1 -1
  219. package/dist/services/side-chats.d.ts +3 -0
  220. package/dist/services/side-chats.d.ts.map +1 -1
  221. package/dist/services/side-chats.js +30 -15
  222. package/dist/services/side-chats.js.map +1 -1
  223. package/dist/services/website-metadata.d.ts +3 -0
  224. package/dist/services/website-metadata.d.ts.map +1 -1
  225. package/dist/services/website-metadata.js +331 -88
  226. package/dist/services/website-metadata.js.map +1 -1
  227. package/package.json +16 -14
  228. package/resources/bundled-skills/app-builder/SKILL.md +104 -0
  229. package/resources/bundled-skills/app-builder/agents/openai.yaml +14 -0
  230. package/resources/bundled-skills/app-builder/assets/scaffold/.env.example +3 -0
  231. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/%5F%5Frudder/health/route.ts +20 -0
  232. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/contacts/[id]/route.ts +39 -0
  233. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/contacts/route.ts +31 -0
  234. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/data/export/route.ts +19 -0
  235. package/resources/bundled-skills/app-builder/assets/scaffold/app/api/data/import/route.ts +40 -0
  236. package/resources/bundled-skills/app-builder/assets/scaffold/app/globals.css +32 -0
  237. package/resources/bundled-skills/app-builder/assets/scaffold/app/layout.tsx +15 -0
  238. package/resources/bundled-skills/app-builder/assets/scaffold/app/page.tsx +9 -0
  239. package/resources/bundled-skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +292 -0
  240. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/button.tsx +42 -0
  241. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/card.tsx +19 -0
  242. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/input.tsx +17 -0
  243. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/label.tsx +6 -0
  244. package/resources/bundled-skills/app-builder/assets/scaffold/data/.gitkeep +1 -0
  245. package/resources/bundled-skills/app-builder/assets/scaffold/drizzle.config.ts +18 -0
  246. package/resources/bundled-skills/app-builder/assets/scaffold/instrumentation.ts +5 -0
  247. package/resources/bundled-skills/app-builder/assets/scaffold/lib/data-transfer.ts +44 -0
  248. package/resources/bundled-skills/app-builder/assets/scaffold/lib/db/client.ts +78 -0
  249. package/resources/bundled-skills/app-builder/assets/scaffold/lib/db/schema.ts +35 -0
  250. package/resources/bundled-skills/app-builder/assets/scaffold/lib/domain.ts +18 -0
  251. package/resources/bundled-skills/app-builder/assets/scaffold/lib/jobs/runner.ts +56 -0
  252. package/resources/bundled-skills/app-builder/assets/scaffold/lib/utils.ts +6 -0
  253. package/resources/bundled-skills/app-builder/assets/scaffold/migrations/0000_app_builder_foundation.sql +27 -0
  254. package/resources/bundled-skills/app-builder/assets/scaffold/migrations/meta/_journal.json +13 -0
  255. package/resources/bundled-skills/app-builder/assets/scaffold/next-env.d.ts +6 -0
  256. package/resources/bundled-skills/app-builder/assets/scaffold/next.config.ts +8 -0
  257. package/resources/bundled-skills/app-builder/assets/scaffold/package.json +47 -0
  258. package/resources/bundled-skills/app-builder/assets/scaffold/playwright.config.ts +30 -0
  259. package/resources/bundled-skills/app-builder/assets/scaffold/pnpm-lock.yaml +2687 -0
  260. package/resources/bundled-skills/app-builder/assets/scaffold/postcss.config.mjs +5 -0
  261. package/resources/bundled-skills/app-builder/assets/scaffold/rudder.app.json +32 -0
  262. package/resources/bundled-skills/app-builder/assets/scaffold/scripts/migrate.ts +21 -0
  263. package/resources/bundled-skills/app-builder/assets/scaffold/scripts/seed.ts +31 -0
  264. package/resources/bundled-skills/app-builder/assets/scaffold/scripts/snapshot.ts +17 -0
  265. package/resources/bundled-skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +50 -0
  266. package/resources/bundled-skills/app-builder/assets/scaffold/tests/unit/data-transfer.test.ts +28 -0
  267. package/resources/bundled-skills/app-builder/assets/scaffold/tests/unit/domain.test.ts +23 -0
  268. package/resources/bundled-skills/app-builder/assets/scaffold/tsconfig.json +41 -0
  269. package/resources/bundled-skills/app-builder/assets/scaffold/vitest.config.ts +14 -0
  270. package/resources/bundled-skills/app-builder/evals/evals.json +65 -0
  271. package/resources/bundled-skills/app-builder/references/data-safety.md +38 -0
  272. package/resources/bundled-skills/app-builder/references/design-guidelines.md +20 -0
  273. package/resources/bundled-skills/app-builder/references/migrations-and-promotion.md +36 -0
  274. package/resources/bundled-skills/app-builder/references/scaffold-contract.md +77 -0
  275. package/resources/bundled-skills/app-builder/references/verification.md +23 -0
  276. package/resources/bundled-skills/app-builder/scripts/scaffold.mjs +58 -0
  277. package/resources/bundled-skills/app-builder/scripts/validate-manifest.mjs +63 -0
  278. package/skills/app-builder/SKILL.md +104 -0
  279. package/skills/app-builder/agents/openai.yaml +14 -0
  280. package/skills/app-builder/assets/scaffold/.env.example +3 -0
  281. package/skills/app-builder/assets/scaffold/app/api/%5F%5Frudder/health/route.ts +20 -0
  282. package/skills/app-builder/assets/scaffold/app/api/contacts/[id]/route.ts +39 -0
  283. package/skills/app-builder/assets/scaffold/app/api/contacts/route.ts +31 -0
  284. package/skills/app-builder/assets/scaffold/app/api/data/export/route.ts +19 -0
  285. package/skills/app-builder/assets/scaffold/app/api/data/import/route.ts +40 -0
  286. package/skills/app-builder/assets/scaffold/app/globals.css +32 -0
  287. package/skills/app-builder/assets/scaffold/app/layout.tsx +15 -0
  288. package/skills/app-builder/assets/scaffold/app/page.tsx +9 -0
  289. package/skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +292 -0
  290. package/skills/app-builder/assets/scaffold/components/ui/button.tsx +42 -0
  291. package/skills/app-builder/assets/scaffold/components/ui/card.tsx +19 -0
  292. package/skills/app-builder/assets/scaffold/components/ui/input.tsx +17 -0
  293. package/skills/app-builder/assets/scaffold/components/ui/label.tsx +6 -0
  294. package/skills/app-builder/assets/scaffold/data/.gitkeep +1 -0
  295. package/skills/app-builder/assets/scaffold/drizzle.config.ts +18 -0
  296. package/skills/app-builder/assets/scaffold/instrumentation.ts +5 -0
  297. package/skills/app-builder/assets/scaffold/lib/data-transfer.ts +44 -0
  298. package/skills/app-builder/assets/scaffold/lib/db/client.ts +78 -0
  299. package/skills/app-builder/assets/scaffold/lib/db/schema.ts +35 -0
  300. package/skills/app-builder/assets/scaffold/lib/domain.ts +18 -0
  301. package/skills/app-builder/assets/scaffold/lib/jobs/runner.ts +56 -0
  302. package/skills/app-builder/assets/scaffold/lib/utils.ts +6 -0
  303. package/skills/app-builder/assets/scaffold/migrations/0000_app_builder_foundation.sql +27 -0
  304. package/skills/app-builder/assets/scaffold/migrations/meta/_journal.json +13 -0
  305. package/skills/app-builder/assets/scaffold/next-env.d.ts +6 -0
  306. package/skills/app-builder/assets/scaffold/next.config.ts +8 -0
  307. package/skills/app-builder/assets/scaffold/package.json +47 -0
  308. package/skills/app-builder/assets/scaffold/playwright.config.ts +30 -0
  309. package/skills/app-builder/assets/scaffold/pnpm-lock.yaml +2687 -0
  310. package/skills/app-builder/assets/scaffold/postcss.config.mjs +5 -0
  311. package/skills/app-builder/assets/scaffold/rudder.app.json +32 -0
  312. package/skills/app-builder/assets/scaffold/scripts/migrate.ts +21 -0
  313. package/skills/app-builder/assets/scaffold/scripts/seed.ts +31 -0
  314. package/skills/app-builder/assets/scaffold/scripts/snapshot.ts +17 -0
  315. package/skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +50 -0
  316. package/skills/app-builder/assets/scaffold/tests/unit/data-transfer.test.ts +28 -0
  317. package/skills/app-builder/assets/scaffold/tests/unit/domain.test.ts +23 -0
  318. package/skills/app-builder/assets/scaffold/tsconfig.json +41 -0
  319. package/skills/app-builder/assets/scaffold/vitest.config.ts +14 -0
  320. package/skills/app-builder/evals/evals.json +65 -0
  321. package/skills/app-builder/references/data-safety.md +38 -0
  322. package/skills/app-builder/references/design-guidelines.md +20 -0
  323. package/skills/app-builder/references/migrations-and-promotion.md +36 -0
  324. package/skills/app-builder/references/scaffold-contract.md +77 -0
  325. package/skills/app-builder/references/verification.md +23 -0
  326. package/skills/app-builder/scripts/scaffold.mjs +58 -0
  327. package/skills/app-builder/scripts/validate-manifest.mjs +63 -0
  328. package/ui-dist/assets/LibraryLiveSurface-DeUIV5N_.js +1 -0
  329. package/ui-dist/assets/{abnfDiagram-VRR7QNED-BdM6grBK.js → abnfDiagram-VRR7QNED-BaoLM9Pd.js} +1 -1
  330. package/ui-dist/assets/{arc-B8Tukeix.js → arc-Cx0L6o2z.js} +1 -1
  331. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-DP_lYJIS.js → architectureDiagram-ZJ3FMSHR-zVfZP7v4.js} +1 -1
  332. package/ui-dist/assets/{blockDiagram-677ZJIJ3-D_QKZNjP.js → blockDiagram-677ZJIJ3-BiWWFP9R.js} +1 -1
  333. package/ui-dist/assets/{c4Diagram-LMCZKHZV-DJwVavN4.js → c4Diagram-LMCZKHZV-CX65GSJ6.js} +1 -1
  334. package/ui-dist/assets/channel-CHc3ibOz.js +1 -0
  335. package/ui-dist/assets/{chunk-2Q5K7J3B-BjdKg2cx.js → chunk-2Q5K7J3B-CH6PlelX.js} +1 -1
  336. package/ui-dist/assets/{chunk-32BRIVSS-C_u4l725.js → chunk-32BRIVSS-C7ZEXYO8.js} +1 -1
  337. package/ui-dist/assets/{chunk-5VM5RSS4-DtzEQCc9.js → chunk-5VM5RSS4-CyInWuue.js} +1 -1
  338. package/ui-dist/assets/{chunk-EX3LRPZG-Ds_gCBVz.js → chunk-EX3LRPZG-D58qFIdM.js} +1 -1
  339. package/ui-dist/assets/{chunk-JWPE2WC7-CHX9VuQf.js → chunk-JWPE2WC7-CJ3Qx30f.js} +1 -1
  340. package/ui-dist/assets/{chunk-MOJQB5TN-D_pj9OWe.js → chunk-MOJQB5TN-BO4bKVLH.js} +1 -1
  341. package/ui-dist/assets/{chunk-RYQCIY6F-aMYmnUaJ.js → chunk-RYQCIY6F-DxBmxcYc.js} +1 -1
  342. package/ui-dist/assets/{chunk-V7JOEXUC-Db3LrL-P.js → chunk-V7JOEXUC-Chdnd6Lq.js} +1 -1
  343. package/ui-dist/assets/{chunk-VR4S4FIN-BssuUpMr.js → chunk-VR4S4FIN-GkW_x_pD.js} +1 -1
  344. package/ui-dist/assets/{chunk-XXDRQBXY-DVzY-_Tv.js → chunk-XXDRQBXY-BJyBGcSF.js} +1 -1
  345. package/ui-dist/assets/classDiagram-OUVF2IWQ-9iLZpz3t.js +1 -0
  346. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-9iLZpz3t.js +1 -0
  347. package/ui-dist/assets/{cose-bilkent-JH36ORCC-Ch-fM2pW.js → cose-bilkent-JH36ORCC-BWP2r8TX.js} +1 -1
  348. package/ui-dist/assets/{cynefin-VYW2F7L2-C7gJeesr.js → cynefin-VYW2F7L2-C08dfI3w.js} +1 -1
  349. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-BwMpD6aZ.js → cynefinDiagram-TSTJHNR4-h6dXjTaO.js} +1 -1
  350. package/ui-dist/assets/{dagre-VKFMJZFB-8TgHu2tj.js → dagre-VKFMJZFB-CWDgoFQD.js} +1 -1
  351. package/ui-dist/assets/{diagram-FQU43EPY-ChODej8C.js → diagram-FQU43EPY-CK1j48d-.js} +1 -1
  352. package/ui-dist/assets/{diagram-G47NLZAW-CVq1GXvV.js → diagram-G47NLZAW-CoSiG57q.js} +1 -1
  353. package/ui-dist/assets/{diagram-NH7WQ7WH-D1IO7xe3.js → diagram-NH7WQ7WH-DRG-xXGQ.js} +1 -1
  354. package/ui-dist/assets/{diagram-OA4YK3LP-CKlttJto.js → diagram-OA4YK3LP-D04mMwYs.js} +1 -1
  355. package/ui-dist/assets/{diagram-WEI45ONY-DigjsORo.js → diagram-WEI45ONY-ZkC1ZQMq.js} +1 -1
  356. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-CZwO7RGO.js → ebnfDiagram-CCIWWBDH-BxMIw9PZ.js} +1 -1
  357. package/ui-dist/assets/{erDiagram-Q63AITRT-C3fFVPod.js → erDiagram-Q63AITRT-B4Zwbn84.js} +1 -1
  358. package/ui-dist/assets/{flowDiagram-23GEKE2U-CpccWfFo.js → flowDiagram-23GEKE2U-D5pYz8Px.js} +1 -1
  359. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-g3KL--4W.js → ganttDiagram-NO4QXBWP-RSramodc.js} +1 -1
  360. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-Bn59AW1B.js → gitGraphDiagram-IHSO6WYX-BFQfCkMd.js} +1 -1
  361. package/ui-dist/assets/{graph-LZSMW9ku.js → graph-DuSS8poS.js} +1 -1
  362. package/ui-dist/assets/{index-CPIoQKcG.js → index-5tzBDABj.js} +1 -1
  363. package/ui-dist/assets/index-9U78M9Np.js +1 -0
  364. package/ui-dist/assets/index-B-NP8fOI.js +1 -0
  365. package/ui-dist/assets/index-BADa8mxG.js +1 -0
  366. package/ui-dist/assets/index-BGcgy63z.js +1 -0
  367. package/ui-dist/assets/index-BKXUkeeU.js +1 -0
  368. package/ui-dist/assets/{index-CrU5g0XU.js → index-BU0KLNTt.js} +588 -558
  369. package/ui-dist/assets/index-BpvytaKr.js +1 -0
  370. package/ui-dist/assets/index-CHbXMgL0.js +7 -0
  371. package/ui-dist/assets/index-Cyq94-QT.js +1 -0
  372. package/ui-dist/assets/{index-BI9d3zXw.js → index-DRbt_Svi.js} +2 -2
  373. package/ui-dist/assets/index-De3vaw_P.js +1 -0
  374. package/ui-dist/assets/index-DzdRgH-y.js +1 -0
  375. package/ui-dist/assets/index-QrRs-r-4.css +1 -0
  376. package/ui-dist/assets/index-olsHIcXs.js +1 -0
  377. package/ui-dist/assets/index-wZlfBtwh.js +1 -0
  378. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-uxPSmmcv.js → infoDiagram-FWYZ7A6U-f5i4pO3J.js} +1 -1
  379. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-BeKQmaaL.js → ishikawaDiagram-FXEZZL3T-CVEdv5i_.js} +1 -1
  380. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-Cnqh1Fyp.js → journeyDiagram-5HDEW3XC-Cvp7vWcy.js} +1 -1
  381. package/ui-dist/assets/{kanban-definition-HUTT4EX6-DRvoG8e7.js → kanban-definition-HUTT4EX6-C6dURjjK.js} +1 -1
  382. package/ui-dist/assets/{layout-By7bOuHZ.js → layout-DxdOu9MY.js} +1 -1
  383. package/ui-dist/assets/{linear-D-kfwm1O.js → linear-CRi8RB_7.js} +1 -1
  384. package/ui-dist/assets/{map-d3zaWROJ.js → map-BlpDrw_l.js} +1 -1
  385. package/ui-dist/assets/{mermaid.core-1JtOXpMA.js → mermaid.core-2Gz9YusH.js} +4 -4
  386. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-D8Jge-_m.js → mindmap-definition-LN4V7U3C-BmSjJLTI.js} +1 -1
  387. package/ui-dist/assets/{pegDiagram-2B236MQR-UCd9-Gfd.js → pegDiagram-2B236MQR-BTUboUw8.js} +1 -1
  388. package/ui-dist/assets/{pieDiagram-ENE6RG2P-DffPYn-P.js → pieDiagram-ENE6RG2P-CVPP8SYv.js} +1 -1
  389. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-Ds1lQ25G.js → quadrantDiagram-ABIIQ3AL-BG4FmirT.js} +1 -1
  390. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-P_pLStbY.js → railroadDiagram-RFXS5EU6-DFRNo9hF.js} +1 -1
  391. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-E_1U7w_T.js → requirementDiagram-TGXJPOKE-DpnVF9qP.js} +1 -1
  392. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-DFgwdZz7.js → sankeyDiagram-HTMAVEWB-D1ETFdSE.js} +1 -1
  393. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CbI2EnTH.js → sequenceDiagram-DBY2YBRQ-CNqzB_AL.js} +1 -1
  394. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-Axl0diWc.js → sizeCapture-X5ZJPWSS-DuoMCjdH.js} +1 -1
  395. package/ui-dist/assets/{stateDiagram-2N3HPSRC-DxZLENhX.js → stateDiagram-2N3HPSRC-fmHeKL8l.js} +1 -1
  396. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DajAVikh.js +1 -0
  397. package/ui-dist/assets/swimlanes-5IMT3BWC-hjosaIhT.js +2 -0
  398. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DJjG9OJ-.js +8 -0
  399. package/ui-dist/assets/{timeline-definition-FHXFAJF6-B0Ha5bZS.js → timeline-definition-FHXFAJF6-xoMEClc-.js} +1 -1
  400. package/ui-dist/assets/{vennDiagram-L72KCM5P-BskD_1N5.js → vennDiagram-L72KCM5P-sdSXQfX-.js} +1 -1
  401. package/ui-dist/assets/{wardleyDiagram-EHGQE667-1wE4bYQ8.js → wardleyDiagram-EHGQE667-BbQBSKlD.js} +1 -1
  402. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-DfcOAfb8.js → xychartDiagram-FW5EYKEG-BfAtadyK.js} +1 -1
  403. package/ui-dist/index.html +2 -2
  404. package/ui-dist/assets/LibraryLiveSurface-CUgoFYvG.js +0 -1
  405. package/ui-dist/assets/channel-Cd4sqN24.js +0 -1
  406. package/ui-dist/assets/classDiagram-OUVF2IWQ-Bi3tsM-q.js +0 -1
  407. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-Bi3tsM-q.js +0 -1
  408. package/ui-dist/assets/index-BG46qMXw.js +0 -1
  409. package/ui-dist/assets/index-BLUJslyp.js +0 -1
  410. package/ui-dist/assets/index-BQv2nBgV.js +0 -1
  411. package/ui-dist/assets/index-BkbdH2n-.js +0 -1
  412. package/ui-dist/assets/index-BqhkUdAT.js +0 -1
  413. package/ui-dist/assets/index-C-qWhcfC.css +0 -1
  414. package/ui-dist/assets/index-C3e6slEn.js +0 -1
  415. package/ui-dist/assets/index-C5hYxLM9.js +0 -1
  416. package/ui-dist/assets/index-CLETiubs.js +0 -7
  417. package/ui-dist/assets/index-CLnCobCG.js +0 -1
  418. package/ui-dist/assets/index-CQLLvEua.js +0 -1
  419. package/ui-dist/assets/index-CVwGLbgm.js +0 -1
  420. package/ui-dist/assets/index-ChTD9DV4.js +0 -1
  421. package/ui-dist/assets/index-CiyYjAsy.js +0 -1
  422. package/ui-dist/assets/index-EqVfudOJ.js +0 -1
  423. package/ui-dist/assets/index-ZFb9ZHAH.js +0 -6
  424. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-KzYOpJ1T.js +0 -1
  425. package/ui-dist/assets/swimlanes-5IMT3BWC-1G26qvUm.js +0 -2
  426. package/ui-dist/assets/swimlanesDiagram-G3AALYLV--Vi3kUvi.js +0 -8
@@ -1,18 +1,23 @@
1
1
  import { resolveKnownWebsiteIcon } from "@rudderhq/shared";
2
+ import ipaddr from "ipaddr.js";
2
3
  import { JSDOM } from "jsdom";
3
4
  import { createHash } from "node:crypto";
4
5
  import { lookup } from "node:dns/promises";
5
- import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
6
+ import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
6
7
  import { isIP } from "node:net";
7
8
  import path from "node:path";
9
+ import { Agent } from "undici";
8
10
  import { resolveRudderInstanceRoot } from "../home-paths.js";
9
11
  const MAX_HTML_BYTES = 256 * 1024;
10
12
  const MAX_ICON_BYTES = 512 * 1024;
13
+ const MAX_PAGE_TITLE_CHARACTERS = 160;
11
14
  const FETCH_TIMEOUT_MS = 5_000;
12
15
  const MAX_REDIRECTS = 5;
13
16
  const SUCCESS_CACHE_TTL_MS = 30 * 60_000;
14
17
  const FAILURE_CACHE_TTL_MS = 5 * 60_000;
18
+ const MAX_METADATA_CACHE_ENTRIES = 128;
15
19
  const ICON_DISK_CACHE_TTL_MS = 7 * 24 * 60 * 60_000;
20
+ const MAX_ICON_DISK_CACHE_ENTRIES = 128;
16
21
  const FALLBACK_FAVICON_SIZE = "64";
17
22
  const IMAGE_CONTENT_TYPES = new Set([
18
23
  "image/gif",
@@ -26,9 +31,6 @@ const IMAGE_CONTENT_TYPES = new Set([
26
31
  ]);
27
32
  function isBenchmarkNetworkIpAddress(value) {
28
33
  const normalized = value.replace(/^\[|\]$/gu, "").toLowerCase();
29
- const ipv4Mapped = normalized.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u);
30
- if (ipv4Mapped)
31
- return isBenchmarkNetworkIpAddress(ipv4Mapped[1]);
32
34
  const parts = normalized.split(".");
33
35
  if (parts.length !== 4)
34
36
  return false;
@@ -40,36 +42,17 @@ function isBenchmarkNetworkIpAddress(value) {
40
42
  }
41
43
  function isPrivateIpAddress(value) {
42
44
  const normalized = value.replace(/^\[|\]$/gu, "").toLowerCase();
43
- const ipv4Mapped = normalized.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u);
44
- if (ipv4Mapped)
45
- return isPrivateIpAddress(ipv4Mapped[1]);
46
- if (normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fc") || normalized.startsWith("fd"))
47
- return true;
48
- const parts = normalized.split(".");
49
- if (parts.length !== 4)
50
- return false;
51
- const octets = parts.map((part) => Number(part));
52
- if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255))
45
+ if (!ipaddr.isValid(normalized))
53
46
  return false;
54
- const [a, b] = octets;
55
- if (a === 10 || a === 127 || a === 0)
56
- return true;
57
- if (a === 100 && b >= 64 && b <= 127)
58
- return true;
59
- if (a === 169 && b === 254)
60
- return true;
61
- if (a === 172 && b >= 16 && b <= 31)
62
- return true;
63
- if (a === 192 && b === 0)
64
- return true;
65
- if (a === 192 && b === 168)
66
- return true;
67
- if (isBenchmarkNetworkIpAddress(normalized))
68
- return true;
69
- return false;
70
- }
71
- function isBlockedResolvedIpAddress(value) {
72
- return isPrivateIpAddress(value) && !isBenchmarkNetworkIpAddress(value);
47
+ const address = ipaddr.process(normalized);
48
+ if (address.kind() === "ipv4") {
49
+ return address.range() !== "unicast"
50
+ || isBenchmarkNetworkIpAddress(address.toString());
51
+ }
52
+ const bytes = address.toByteArray();
53
+ const isDeprecatedSiteLocal = bytes[0] === 0xfe
54
+ && ((bytes[1] ?? 0) & 0xc0) === 0xc0;
55
+ return address.range() !== "unicast" || isDeprecatedSiteLocal;
73
56
  }
74
57
  function isPrivateHostname(hostname) {
75
58
  const normalized = hostname.replace(/^\[|\]$/gu, "").toLowerCase();
@@ -101,6 +84,9 @@ export function parsePublicHttpUrl(value, options = {}) {
101
84
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
102
85
  throw new Error("Only http and https URLs can be inspected");
103
86
  }
87
+ if (parsed.username || parsed.password) {
88
+ throw new Error("Credentialed URLs cannot be inspected");
89
+ }
104
90
  if (!options.allowPrivateHosts && isPrivateHostname(parsed.hostname)) {
105
91
  throw new Error("Private network URLs cannot be inspected");
106
92
  }
@@ -110,24 +96,120 @@ export function parsePublicHttpUrl(value, options = {}) {
110
96
  function resolveWebsiteIconCacheDir() {
111
97
  return path.resolve(resolveRudderInstanceRoot(), "data", "website-icons");
112
98
  }
113
- async function assertPublicResolvedHost(url, options) {
114
- if (options.allowPrivateHosts || options.fetchImpl || isIP(url.hostname.replace(/^\[|\]$/gu, "")))
115
- return;
116
- const addresses = await lookup(url.hostname, { all: true, verbatim: true });
117
- const allowBenchmarkNetworkResolution = isPublicDnsHostname(url.hostname);
118
- if (addresses.some((address) => isPrivateIpAddress(address.address) && (!allowBenchmarkNetworkResolution || !isBenchmarkNetworkIpAddress(address.address)))) {
99
+ async function awaitWithAbort(promise, signal, onLateValue) {
100
+ const disposeLateValue = (value) => {
101
+ if (!onLateValue)
102
+ return;
103
+ void Promise.resolve(onLateValue(value)).catch(() => undefined);
104
+ };
105
+ if (signal.aborted) {
106
+ void promise.then(disposeLateValue, () => undefined);
107
+ throw signal.reason;
108
+ }
109
+ return new Promise((resolve, reject) => {
110
+ let settled = false;
111
+ const onAbort = () => {
112
+ if (settled)
113
+ return;
114
+ settled = true;
115
+ signal.removeEventListener("abort", onAbort);
116
+ reject(signal.reason);
117
+ };
118
+ signal.addEventListener("abort", onAbort, { once: true });
119
+ void promise.then((value) => {
120
+ if (settled) {
121
+ disposeLateValue(value);
122
+ return;
123
+ }
124
+ settled = true;
125
+ signal.removeEventListener("abort", onAbort);
126
+ resolve(value);
127
+ }, (error) => {
128
+ if (settled)
129
+ return;
130
+ settled = true;
131
+ signal.removeEventListener("abort", onAbort);
132
+ reject(error);
133
+ });
134
+ });
135
+ }
136
+ async function resolveValidatedHostAddress(url, options, signal) {
137
+ const hostname = url.hostname.replace(/^\[|\]$/gu, "");
138
+ if (options.fetchImpl || isIP(hostname))
139
+ return null;
140
+ const addresses = await awaitWithAbort(lookup(url.hostname, { all: true, verbatim: true }), signal);
141
+ if (addresses.length === 0)
142
+ throw new Error("Website hostname did not resolve");
143
+ if (!options.allowPrivateHosts
144
+ && addresses.some((address) => isPrivateIpAddress(address.address))) {
119
145
  throw new Error("Private network URLs cannot be inspected");
120
146
  }
147
+ return addresses[0] ?? null;
148
+ }
149
+ function createPinnedDispatcher(resolvedHost) {
150
+ if (!resolvedHost)
151
+ return null;
152
+ return new Agent({
153
+ connect: {
154
+ autoSelectFamily: false,
155
+ lookup: (_hostname, _options, callback) => {
156
+ callback(null, resolvedHost.address, resolvedHost.family);
157
+ },
158
+ },
159
+ });
160
+ }
161
+ async function cancelResponseBody(response) {
162
+ const body = response?.body;
163
+ if (!body || body.locked)
164
+ return;
165
+ try {
166
+ await body.cancel();
167
+ }
168
+ catch {
169
+ // Fetch aborts and peer disconnects can close the body before cancellation settles.
170
+ }
171
+ }
172
+ async function closeDispatcher(dispatcher, aborted) {
173
+ if (!dispatcher)
174
+ return;
175
+ try {
176
+ if (aborted)
177
+ await dispatcher.destroy();
178
+ else
179
+ await dispatcher.close();
180
+ }
181
+ catch {
182
+ // Cleanup must not replace the request result with a connection teardown error.
183
+ }
121
184
  }
122
185
  async function fetchWithTimeout(url, options, init) {
123
186
  let currentUrl = parsePublicHttpUrl(url.href, options);
124
187
  const fetchImpl = options.fetchImpl ?? fetch;
125
- for (let redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount += 1) {
126
- await assertPublicResolvedHost(currentUrl, options);
127
- const controller = new AbortController();
128
- const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
129
- try {
130
- const response = await fetchImpl(currentUrl.href, {
188
+ const controller = new AbortController();
189
+ const timeout = setTimeout(() => {
190
+ controller.abort(new Error("Website metadata request timed out"));
191
+ }, FETCH_TIMEOUT_MS);
192
+ timeout.unref?.();
193
+ let activeResponse = null;
194
+ let activeDispatcher = null;
195
+ let released = false;
196
+ const release = async () => {
197
+ if (released)
198
+ return;
199
+ released = true;
200
+ clearTimeout(timeout);
201
+ await cancelResponseBody(activeResponse);
202
+ activeResponse = null;
203
+ await closeDispatcher(activeDispatcher, controller.signal.aborted);
204
+ activeDispatcher = null;
205
+ };
206
+ try {
207
+ for (let redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount += 1) {
208
+ const resolvedHost = await resolveValidatedHostAddress(currentUrl, options, controller.signal);
209
+ if (controller.signal.aborted)
210
+ throw controller.signal.reason;
211
+ activeDispatcher = createPinnedDispatcher(resolvedHost);
212
+ const requestInit = {
131
213
  redirect: "manual",
132
214
  ...init,
133
215
  signal: controller.signal,
@@ -136,25 +218,59 @@ async function fetchWithTimeout(url, options, init) {
136
218
  "user-agent": "RudderWebsiteMetadata/1.0",
137
219
  ...(init?.headers ?? {}),
138
220
  },
139
- });
140
- if (response.status < 300 || response.status >= 400)
141
- return { response, url: currentUrl };
221
+ ...(activeDispatcher ? { dispatcher: activeDispatcher } : {}),
222
+ };
223
+ const response = await awaitWithAbort(fetchImpl(currentUrl.href, { ...requestInit }), controller.signal, cancelResponseBody);
224
+ activeResponse = response;
225
+ if (response.status < 300 || response.status >= 400) {
226
+ return {
227
+ response,
228
+ url: currentUrl,
229
+ signal: controller.signal,
230
+ release,
231
+ };
232
+ }
142
233
  const location = response.headers.get("location");
143
- if (!location)
144
- return { response, url: currentUrl };
234
+ if (!location) {
235
+ return {
236
+ response,
237
+ url: currentUrl,
238
+ signal: controller.signal,
239
+ release,
240
+ };
241
+ }
242
+ await cancelResponseBody(activeResponse);
243
+ activeResponse = null;
244
+ await closeDispatcher(activeDispatcher, controller.signal.aborted);
245
+ activeDispatcher = null;
145
246
  currentUrl = parsePublicHttpUrl(new URL(location, currentUrl).href, options);
146
247
  }
147
- finally {
148
- clearTimeout(timeout);
149
- }
248
+ throw new Error("Website metadata redirect limit exceeded");
249
+ }
250
+ catch (error) {
251
+ await release();
252
+ throw error;
150
253
  }
151
- throw new Error("Website metadata redirect limit exceeded");
152
254
  }
153
- function metadataCacheKey(url) {
154
- return url.href;
255
+ function metadataCacheKey(url, purpose) {
256
+ return `${purpose}:${url.href}`;
155
257
  }
156
258
  const metadataCache = new Map();
157
259
  const metadataInflight = new Map();
260
+ function pruneMetadataCache(now = Date.now()) {
261
+ for (const [key, entry] of metadataCache) {
262
+ if (entry.expiresAt <= now)
263
+ metadataCache.delete(key);
264
+ }
265
+ }
266
+ function makeRoomInMetadataCache() {
267
+ while (metadataCache.size >= MAX_METADATA_CACHE_ENTRIES) {
268
+ const oldestKey = metadataCache.keys().next().value;
269
+ if (!oldestKey)
270
+ break;
271
+ metadataCache.delete(oldestKey);
272
+ }
273
+ }
158
274
  async function readLimitedBuffer(response, maxBytes, options = {}) {
159
275
  const body = response.body;
160
276
  if (!body)
@@ -162,9 +278,15 @@ async function readLimitedBuffer(response, maxBytes, options = {}) {
162
278
  const reader = body.getReader();
163
279
  const chunks = [];
164
280
  let total = 0;
281
+ const onAbort = () => {
282
+ void reader.cancel(options.signal?.reason).catch(() => undefined);
283
+ };
284
+ options.signal?.addEventListener("abort", onAbort, { once: true });
165
285
  try {
166
286
  while (true) {
167
287
  const { value, done } = await reader.read();
288
+ if (options.signal?.aborted)
289
+ throw options.signal.reason;
168
290
  if (done)
169
291
  break;
170
292
  if (!value)
@@ -178,12 +300,14 @@ async function readLimitedBuffer(response, maxBytes, options = {}) {
178
300
  await reader.cancel();
179
301
  break;
180
302
  }
303
+ await reader.cancel();
181
304
  throw new Error("Response exceeds metadata size limit");
182
305
  }
183
306
  chunks.push(Buffer.from(value));
184
307
  }
185
308
  }
186
309
  finally {
310
+ options.signal?.removeEventListener("abort", onAbort);
187
311
  reader.releaseLock();
188
312
  }
189
313
  return Buffer.concat(chunks);
@@ -195,6 +319,7 @@ function isInspectableUrlFailure(error) {
195
319
  if (!(error instanceof Error))
196
320
  return false;
197
321
  return error.message === "Only http and https URLs can be inspected"
322
+ || error.message === "Credentialed URLs cannot be inspected"
198
323
  || error.message === "Private network URLs cannot be inspected"
199
324
  || error.message === "Website metadata redirect limit exceeded";
200
325
  }
@@ -231,6 +356,32 @@ function readSiteName(document) {
231
356
  const title = document.querySelector("title")?.textContent?.trim();
232
357
  return title || null;
233
358
  }
359
+ function normalizePageTitle(document, value) {
360
+ if (!value)
361
+ return null;
362
+ const template = document.createElement("template");
363
+ template.innerHTML = value;
364
+ template.content.querySelectorAll("script, style, template, noscript").forEach((element) => element.remove());
365
+ const plainText = (template.content.textContent ?? "")
366
+ .replace(/[\s\p{Cc}\p{Cf}]+/gu, " ")
367
+ .trim();
368
+ if (!plainText)
369
+ return null;
370
+ return Array.from(plainText).slice(0, MAX_PAGE_TITLE_CHARACTERS).join("");
371
+ }
372
+ function readPageTitle(document) {
373
+ const candidates = [
374
+ document.querySelector('meta[property="og:title"]')?.getAttribute("content"),
375
+ document.querySelector('meta[name="twitter:title"]')?.getAttribute("content"),
376
+ document.querySelector("title")?.textContent,
377
+ ];
378
+ for (const candidate of candidates) {
379
+ const title = normalizePageTitle(document, candidate);
380
+ if (title)
381
+ return title;
382
+ }
383
+ return null;
384
+ }
234
385
  function iconPriority(element) {
235
386
  const rel = new Set(linkRelTokens(element));
236
387
  if (rel.has("icon") && rel.has("shortcut"))
@@ -263,19 +414,24 @@ function findDeclaredIcon(document, baseUrl, options) {
263
414
  return null;
264
415
  }
265
416
  async function validateIconUrl(iconHref, options) {
417
+ let result = null;
266
418
  try {
267
- const { response, url } = await fetchWithTimeout(parsePublicHttpUrl(iconHref, options), options, { method: "GET" });
419
+ result = await fetchWithTimeout(parsePublicHttpUrl(iconHref, options), options, { method: "GET" });
420
+ const { response, signal, url } = result;
268
421
  if (!response.ok)
269
422
  return null;
270
423
  const contentType = contentTypeBase(response.headers.get("content-type"));
271
424
  if (!IMAGE_CONTENT_TYPES.has(contentType))
272
425
  return null;
273
- await readLimitedBuffer(response, MAX_ICON_BYTES);
426
+ await readLimitedBuffer(response, MAX_ICON_BYTES, { signal });
274
427
  return url.href;
275
428
  }
276
429
  catch {
277
430
  return null;
278
431
  }
432
+ finally {
433
+ await result?.release();
434
+ }
279
435
  }
280
436
  async function findImplicitFavicon(baseUrl, options) {
281
437
  const faviconUrl = new URL("/favicon.ico", baseUrl);
@@ -298,8 +454,15 @@ async function findProviderFavicon(pageUrl, options) {
298
454
  async function resolveWebsiteMetadataUncached(value, options) {
299
455
  const pageUrl = parsePublicHttpUrl(value, options);
300
456
  const knownIcon = resolveKnownWebsiteIcon(pageUrl);
301
- if (knownIcon) {
302
- return { url: pageUrl.href, siteName: knownIcon.siteName, iconUrl: knownIcon.iconDataUrl };
457
+ const emptyMetadata = {
458
+ url: pageUrl.href,
459
+ siteName: knownIcon?.siteName ?? null,
460
+ pageTitle: null,
461
+ iconUrl: knownIcon?.iconDataUrl ?? null,
462
+ };
463
+ // Preview rendering keeps bundled icons zero-fetch; authoring falls through to resolve a title.
464
+ if (knownIcon && (options.purpose ?? "preview") === "preview") {
465
+ return emptyMetadata;
303
466
  }
304
467
  let pageResult;
305
468
  try {
@@ -308,25 +471,46 @@ async function resolveWebsiteMetadataUncached(value, options) {
308
471
  catch (error) {
309
472
  if (isInspectableUrlFailure(error))
310
473
  throw error;
311
- return { url: pageUrl.href, siteName: null, iconUrl: null };
474
+ return emptyMetadata;
312
475
  }
313
476
  const { response, url: finalPageUrl } = pageResult;
314
- if (!response.ok) {
315
- return { url: finalPageUrl.href, siteName: null, iconUrl: null };
477
+ let html;
478
+ try {
479
+ if (!response.ok)
480
+ return emptyMetadata;
481
+ const contentType = contentTypeBase(response.headers.get("content-type"));
482
+ if (contentType && contentType !== "text/html" && contentType !== "application/xhtml+xml") {
483
+ return emptyMetadata;
484
+ }
485
+ html = (await readLimitedBuffer(response, MAX_HTML_BYTES, {
486
+ signal: pageResult.signal,
487
+ truncate: true,
488
+ })).toString("utf8");
316
489
  }
317
- const contentType = contentTypeBase(response.headers.get("content-type"));
318
- if (contentType && contentType !== "text/html" && contentType !== "application/xhtml+xml") {
319
- return { url: finalPageUrl.href, siteName: null, iconUrl: null };
490
+ catch {
491
+ return emptyMetadata;
492
+ }
493
+ finally {
494
+ await pageResult.release();
320
495
  }
321
- const html = (await readLimitedBuffer(response, MAX_HTML_BYTES, { truncate: true })).toString("utf8");
322
496
  const dom = new JSDOM(html, { url: finalPageUrl.href });
323
497
  try {
324
498
  const document = dom.window.document;
499
+ if ((options.purpose ?? "preview") === "authoring") {
500
+ return {
501
+ url: pageUrl.href,
502
+ siteName: knownIcon?.siteName ?? readSiteName(document),
503
+ pageTitle: readPageTitle(document),
504
+ iconUrl: knownIcon?.iconDataUrl ?? null,
505
+ };
506
+ }
325
507
  const declaredIcon = findDeclaredIcon(document, finalPageUrl, options);
326
508
  return {
327
- url: finalPageUrl.href,
328
- siteName: readSiteName(document),
329
- iconUrl: (declaredIcon ? await validateIconUrl(declaredIcon, options) : null)
509
+ url: pageUrl.href,
510
+ siteName: knownIcon?.siteName ?? readSiteName(document),
511
+ pageTitle: readPageTitle(document),
512
+ iconUrl: knownIcon?.iconDataUrl
513
+ ?? (declaredIcon ? await validateIconUrl(declaredIcon, options) : null)
330
514
  ?? await findImplicitFavicon(finalPageUrl, options)
331
515
  ?? await findProviderFavicon(finalPageUrl, options),
332
516
  };
@@ -337,18 +521,26 @@ async function resolveWebsiteMetadataUncached(value, options) {
337
521
  }
338
522
  export async function resolveWebsiteMetadata(value, options = {}) {
339
523
  const pageUrl = parsePublicHttpUrl(value, options);
340
- const key = metadataCacheKey(pageUrl);
524
+ const key = metadataCacheKey(pageUrl, options.purpose ?? "preview");
341
525
  if (!options.allowPrivateHosts && !options.fetchImpl) {
526
+ pruneMetadataCache();
342
527
  const cached = metadataCache.get(key);
343
- if (cached && cached.expiresAt > Date.now())
528
+ if (cached) {
529
+ metadataCache.delete(key);
530
+ metadataCache.set(key, cached);
344
531
  return cached.value;
532
+ }
345
533
  const inflight = metadataInflight.get(key);
346
534
  if (inflight)
347
535
  return inflight;
348
536
  const request = resolveWebsiteMetadataUncached(pageUrl.href, options)
349
537
  .then((metadata) => {
538
+ pruneMetadataCache();
539
+ makeRoomInMetadataCache();
350
540
  metadataCache.set(key, {
351
- expiresAt: Date.now() + (metadata.iconUrl ? SUCCESS_CACHE_TTL_MS : FAILURE_CACHE_TTL_MS),
541
+ expiresAt: Date.now() + (metadata.iconUrl || metadata.pageTitle || metadata.siteName
542
+ ? SUCCESS_CACHE_TTL_MS
543
+ : FAILURE_CACHE_TTL_MS),
352
544
  value: metadata,
353
545
  });
354
546
  return metadata;
@@ -368,20 +560,26 @@ export async function fetchWebsiteIcon(value, options = {}) {
368
560
  if (cached)
369
561
  return cached;
370
562
  }
371
- const { response } = await fetchWithTimeout(iconUrl, options);
372
- if (!response.ok)
373
- return null;
374
- const contentType = contentTypeBase(response.headers.get("content-type"));
375
- if (!IMAGE_CONTENT_TYPES.has(contentType))
376
- return null;
377
- const body = await readLimitedBuffer(response, MAX_ICON_BYTES);
378
- if (body.length <= 0)
379
- return null;
380
- const icon = { contentType, body };
381
- if (!options.allowPrivateHosts && !options.fetchImpl) {
382
- await writeCachedWebsiteIcon(iconUrl.href, icon).catch(() => undefined);
563
+ const result = await fetchWithTimeout(iconUrl, options);
564
+ try {
565
+ const { response, signal } = result;
566
+ if (!response.ok)
567
+ return null;
568
+ const contentType = contentTypeBase(response.headers.get("content-type"));
569
+ if (!IMAGE_CONTENT_TYPES.has(contentType))
570
+ return null;
571
+ const body = await readLimitedBuffer(response, MAX_ICON_BYTES, { signal });
572
+ if (body.length <= 0)
573
+ return null;
574
+ const icon = { contentType, body };
575
+ if (!options.allowPrivateHosts && !options.fetchImpl) {
576
+ await writeCachedWebsiteIcon(iconUrl.href, icon).catch(() => undefined);
577
+ }
578
+ return icon;
579
+ }
580
+ finally {
581
+ await result.release();
383
582
  }
384
- return icon;
385
583
  }
386
584
  function cachedWebsiteIconBasename(iconUrl) {
387
585
  return createHash("sha256").update(iconUrl).digest("hex");
@@ -401,8 +599,13 @@ async function readCachedWebsiteIcon(iconUrl) {
401
599
  readFile(metadataPath, "utf8"),
402
600
  stat(bodyPath),
403
601
  ]);
404
- if (Date.now() - bodyStats.mtimeMs > ICON_DISK_CACHE_TTL_MS)
602
+ if (Date.now() - bodyStats.mtimeMs > ICON_DISK_CACHE_TTL_MS) {
603
+ await Promise.all([
604
+ rm(metadataPath, { force: true }),
605
+ rm(bodyPath, { force: true }),
606
+ ]);
405
607
  return null;
608
+ }
406
609
  const metadata = JSON.parse(rawMetadata);
407
610
  if (metadata.url !== iconUrl || typeof metadata.contentType !== "string")
408
611
  return null;
@@ -417,13 +620,53 @@ async function readCachedWebsiteIcon(iconUrl) {
417
620
  return null;
418
621
  }
419
622
  }
623
+ async function pruneWebsiteIconDiskCache(dir, protectedBasename) {
624
+ const filenames = await readdir(dir);
625
+ const basenames = filenames
626
+ .filter((filename) => /^[a-f0-9]{64}\.json$/u.test(filename))
627
+ .map((filename) => filename.slice(0, -".json".length));
628
+ const entries = await Promise.all(basenames.map(async (basename) => {
629
+ try {
630
+ const [metadataStats, bodyStats] = await Promise.all([
631
+ stat(path.join(dir, `${basename}.json`)),
632
+ stat(path.join(dir, `${basename}.bin`)),
633
+ ]);
634
+ return {
635
+ basename,
636
+ mtimeMs: Math.min(metadataStats.mtimeMs, bodyStats.mtimeMs),
637
+ };
638
+ }
639
+ catch {
640
+ return { basename, mtimeMs: 0 };
641
+ }
642
+ }));
643
+ const now = Date.now();
644
+ const retained = entries
645
+ .filter((entry) => now - entry.mtimeMs <= ICON_DISK_CACHE_TTL_MS)
646
+ .sort((left, right) => {
647
+ if (left.basename === protectedBasename)
648
+ return -1;
649
+ if (right.basename === protectedBasename)
650
+ return 1;
651
+ return right.mtimeMs - left.mtimeMs;
652
+ })
653
+ .slice(0, MAX_ICON_DISK_CACHE_ENTRIES);
654
+ const retainedBasenames = new Set(retained.map((entry) => entry.basename));
655
+ const discarded = entries.filter((entry) => !retainedBasenames.has(entry.basename));
656
+ await Promise.all(discarded.flatMap((entry) => [
657
+ rm(path.join(dir, `${entry.basename}.json`), { force: true }),
658
+ rm(path.join(dir, `${entry.basename}.bin`), { force: true }),
659
+ ]));
660
+ }
420
661
  async function writeCachedWebsiteIcon(iconUrl, icon) {
421
662
  const { metadataPath, bodyPath } = cachedWebsiteIconPaths(iconUrl);
422
- await mkdir(path.dirname(bodyPath), { recursive: true });
663
+ const dir = path.dirname(bodyPath);
664
+ await mkdir(dir, { recursive: true });
423
665
  await Promise.all([
424
666
  writeFile(bodyPath, icon.body),
425
667
  writeFile(metadataPath, `${JSON.stringify({ url: iconUrl, contentType: icon.contentType })}\n`, "utf8"),
426
668
  ]);
669
+ await pruneWebsiteIconDiskCache(dir, path.basename(metadataPath, ".json"));
427
670
  }
428
671
  export function __clearWebsiteMetadataCacheForTests() {
429
672
  metadataCache.clear();