@flamingo-stack/openframe-frontend-core 0.0.296-snapshot.20260621021605 → 0.0.296

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 (276) hide show
  1. package/README.md +0 -9
  2. package/dist/chunk-26PKDALD.js +2379 -0
  3. package/dist/chunk-26PKDALD.js.map +1 -0
  4. package/dist/chunk-3MCHAFHB.js +89 -0
  5. package/dist/chunk-3MCHAFHB.js.map +1 -0
  6. package/dist/{chunk-PI4WSYQV.js → chunk-3ZXUQQL4.js} +2 -2
  7. package/dist/{chunk-WMSTJAZT.cjs → chunk-5E2HOSSH.cjs} +51 -913
  8. package/dist/chunk-5E2HOSSH.cjs.map +1 -0
  9. package/dist/{chunk-IL47XWV5.js → chunk-5P3B2LZW.js} +14 -8
  10. package/dist/{chunk-IL47XWV5.js.map → chunk-5P3B2LZW.js.map} +1 -1
  11. package/dist/chunk-66AANIOC.cjs +619 -0
  12. package/dist/chunk-66AANIOC.cjs.map +1 -0
  13. package/dist/{chunk-AD6C23QY.js → chunk-6GCI7JOE.js} +7 -8
  14. package/dist/{chunk-AD6C23QY.js.map → chunk-6GCI7JOE.js.map} +1 -1
  15. package/dist/chunk-6JINAOI7.cjs +311 -0
  16. package/dist/chunk-6JINAOI7.cjs.map +1 -0
  17. package/dist/{chunk-2QG57XOJ.js → chunk-7RIYT7ZH.js} +205 -1067
  18. package/dist/chunk-7RIYT7ZH.js.map +1 -0
  19. package/dist/{chunk-L6PSSIUQ.cjs → chunk-AQOWFSMB.cjs} +1 -1
  20. package/dist/chunk-AQOWFSMB.cjs.map +1 -0
  21. package/dist/chunk-BOCFIKYS.cjs +3009 -0
  22. package/dist/chunk-BOCFIKYS.cjs.map +1 -0
  23. package/dist/{chunk-54KNMC2R.cjs → chunk-D3LEFMOA.cjs} +3 -3
  24. package/dist/{chunk-54KNMC2R.cjs.map → chunk-D3LEFMOA.cjs.map} +1 -1
  25. package/dist/chunk-D652TJBQ.js +3009 -0
  26. package/dist/chunk-D652TJBQ.js.map +1 -0
  27. package/dist/{chunk-PWQUAVA3.js → chunk-E4XABBSU.js} +98 -338
  28. package/dist/chunk-E4XABBSU.js.map +1 -0
  29. package/dist/{chunk-JALO4TAZ.js → chunk-EL6QLAWX.js} +55 -357
  30. package/dist/chunk-EL6QLAWX.js.map +1 -0
  31. package/dist/{chunk-6C526VNN.cjs → chunk-EYEW6PTA.cjs} +118 -358
  32. package/dist/chunk-EYEW6PTA.cjs.map +1 -0
  33. package/dist/chunk-FQJK446R.js +1606 -0
  34. package/dist/chunk-FQJK446R.js.map +1 -0
  35. package/dist/{chunk-4PSQS3SW.cjs → chunk-GLLDTKZK.cjs} +9 -7
  36. package/dist/chunk-GLLDTKZK.cjs.map +1 -0
  37. package/dist/{chunk-FQOTC3UU.cjs → chunk-IE6OU3WQ.cjs} +16 -318
  38. package/dist/chunk-IE6OU3WQ.cjs.map +1 -0
  39. package/dist/chunk-J54Z3OCR.cjs +1606 -0
  40. package/dist/chunk-J54Z3OCR.cjs.map +1 -0
  41. package/dist/{chunk-PC746XCO.js → chunk-K2PFPBMF.js} +5563 -15048
  42. package/dist/chunk-K2PFPBMF.js.map +1 -0
  43. package/dist/chunk-KXCRGTRN.cjs +2379 -0
  44. package/dist/chunk-KXCRGTRN.cjs.map +1 -0
  45. package/dist/{chunk-IZ7JSBFP.js → chunk-LCNMR277.js} +1 -1
  46. package/dist/chunk-LCNMR277.js.map +1 -0
  47. package/dist/chunk-LFGGF7OT.cjs +449 -0
  48. package/dist/chunk-LFGGF7OT.cjs.map +1 -0
  49. package/dist/chunk-M2OCXTNT.js +311 -0
  50. package/dist/chunk-M2OCXTNT.js.map +1 -0
  51. package/dist/{chunk-L7ULJKG7.js → chunk-MBFWU2EM.js} +10 -6
  52. package/dist/{chunk-L7ULJKG7.js.map → chunk-MBFWU2EM.js.map} +1 -1
  53. package/dist/chunk-ME4EVDFP.js +619 -0
  54. package/dist/chunk-ME4EVDFP.js.map +1 -0
  55. package/dist/chunk-OQ6X7ZOC.js +449 -0
  56. package/dist/chunk-OQ6X7ZOC.js.map +1 -0
  57. package/dist/{chunk-4TLE6VLU.js → chunk-OY7OF7E7.js} +24 -30
  58. package/dist/chunk-OY7OF7E7.js.map +1 -0
  59. package/dist/chunk-POKKCWKF.js +354 -0
  60. package/dist/chunk-POKKCWKF.js.map +1 -0
  61. package/dist/{chunk-GUTS7HGA.cjs → chunk-QHIXS3W2.cjs} +2514 -11999
  62. package/dist/chunk-QHIXS3W2.cjs.map +1 -0
  63. package/dist/chunk-TFSYSWPS.cjs +89 -0
  64. package/dist/chunk-TFSYSWPS.cjs.map +1 -0
  65. package/dist/{chunk-53FUMSZ5.cjs → chunk-W6M2FLLT.cjs} +46 -40
  66. package/dist/chunk-W6M2FLLT.cjs.map +1 -0
  67. package/dist/{chunk-3JIQVE7T.js → chunk-WHMATDVP.js} +15 -9
  68. package/dist/{chunk-3JIQVE7T.js.map → chunk-WHMATDVP.js.map} +1 -1
  69. package/dist/{chunk-YBYI62OE.cjs → chunk-X647HY3F.cjs} +37 -33
  70. package/dist/chunk-X647HY3F.cjs.map +1 -0
  71. package/dist/{chunk-UNVE2SDJ.cjs → chunk-X6BV7MB7.cjs} +31 -37
  72. package/dist/chunk-X6BV7MB7.cjs.map +1 -0
  73. package/dist/{chunk-7OVGB2DQ.cjs → chunk-XREEV72C.cjs} +25 -19
  74. package/dist/chunk-XREEV72C.cjs.map +1 -0
  75. package/dist/chunk-YETA25JW.cjs +354 -0
  76. package/dist/chunk-YETA25JW.cjs.map +1 -0
  77. package/dist/{chunk-FCDQNTDG.cjs → chunk-YIGPRLQY.cjs} +20 -21
  78. package/dist/chunk-YIGPRLQY.cjs.map +1 -0
  79. package/dist/{chunk-X4DOXQRT.js → chunk-ZP4AVIZP.js} +6 -4
  80. package/dist/{chunk-X4DOXQRT.js.map → chunk-ZP4AVIZP.js.map} +1 -1
  81. package/dist/components/chat/index.cjs +18 -8
  82. package/dist/components/chat/index.cjs.map +1 -1
  83. package/dist/components/chat/index.js +85 -75
  84. package/dist/components/contact/index.cjs +15 -8
  85. package/dist/components/contact/index.cjs.map +1 -1
  86. package/dist/components/contact/index.js +14 -7
  87. package/dist/components/docs/doc-viewer.d.ts +2 -39
  88. package/dist/components/docs/doc-viewer.d.ts.map +1 -1
  89. package/dist/components/docs/index.cjs +9 -17
  90. package/dist/components/docs/index.cjs.map +1 -1
  91. package/dist/components/docs/index.d.ts +0 -4
  92. package/dist/components/docs/index.d.ts.map +1 -1
  93. package/dist/components/docs/index.js +8 -16
  94. package/dist/components/docs/use-document-tree.d.ts.map +1 -1
  95. package/dist/components/embeds/embed-iframe.d.ts.map +1 -1
  96. package/dist/components/embeds/index.cjs +15 -38
  97. package/dist/components/embeds/index.cjs.map +1 -1
  98. package/dist/components/embeds/index.d.ts +0 -8
  99. package/dist/components/embeds/index.d.ts.map +1 -1
  100. package/dist/components/embeds/index.js +17 -40
  101. package/dist/components/faq/index.cjs +16 -9
  102. package/dist/components/faq/index.cjs.map +1 -1
  103. package/dist/components/faq/index.js +15 -8
  104. package/dist/components/features/index.cjs +16 -8
  105. package/dist/components/features/index.cjs.map +1 -1
  106. package/dist/components/features/index.js +32 -24
  107. package/dist/components/index.cjs +452 -257
  108. package/dist/components/index.cjs.map +1 -1
  109. package/dist/components/index.js +976 -781
  110. package/dist/components/index.js.map +1 -1
  111. package/dist/components/layout/page-layout.d.ts +1 -10
  112. package/dist/components/layout/page-layout.d.ts.map +1 -1
  113. package/dist/components/layout/title-block.d.ts +1 -17
  114. package/dist/components/layout/title-block.d.ts.map +1 -1
  115. package/dist/components/navigation/index.cjs +15 -7
  116. package/dist/components/navigation/index.cjs.map +1 -1
  117. package/dist/components/navigation/index.js +17 -9
  118. package/dist/components/onboarding-guides/index.cjs +36 -35
  119. package/dist/components/onboarding-guides/index.cjs.map +1 -1
  120. package/dist/components/onboarding-guides/index.js +14 -13
  121. package/dist/components/onboarding-guides/index.js.map +1 -1
  122. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +1 -1
  123. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  124. package/dist/components/related-content/index.cjs +16 -9
  125. package/dist/components/related-content/index.cjs.map +1 -1
  126. package/dist/components/related-content/index.js +15 -8
  127. package/dist/components/shared/dev-section/dev-section-page.d.ts +0 -9
  128. package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -1
  129. package/dist/components/shared/dev-section/dev-section-view.d.ts.map +1 -1
  130. package/dist/components/shared/dev-section/index.d.ts +1 -1
  131. package/dist/components/shared/dev-section/index.d.ts.map +1 -1
  132. package/dist/components/shared/doc-search/use-doc-search.d.ts.map +1 -1
  133. package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -1
  134. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  135. package/dist/components/tickets/index.cjs +112 -100
  136. package/dist/components/tickets/index.cjs.map +1 -1
  137. package/dist/components/tickets/index.js +32 -20
  138. package/dist/components/tickets/index.js.map +1 -1
  139. package/dist/components/ui/file-manager/index.cjs +52 -50
  140. package/dist/components/ui/file-manager/index.cjs.map +1 -1
  141. package/dist/components/ui/file-manager/index.js +6 -4
  142. package/dist/components/ui/file-manager/index.js.map +1 -1
  143. package/dist/components/ui/index.cjs +19 -13
  144. package/dist/components/ui/index.cjs.map +1 -1
  145. package/dist/components/ui/index.d.ts +0 -2
  146. package/dist/components/ui/index.d.ts.map +1 -1
  147. package/dist/components/ui/index.js +139 -133
  148. package/dist/components/ui/release-changelog-section.d.ts +2 -6
  149. package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
  150. package/dist/components/ui/simple-markdown-renderer.d.ts +8 -2
  151. package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
  152. package/dist/contexts/chat-runtime-context.d.ts +0 -14
  153. package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
  154. package/dist/contexts/index.cjs +3 -3
  155. package/dist/contexts/index.js +5 -5
  156. package/dist/embed-shims/index.cjs +3 -3
  157. package/dist/embed-shims/index.cjs.map +1 -1
  158. package/dist/embed-shims/index.js +4 -4
  159. package/dist/hooks/index.cjs +9 -4
  160. package/dist/hooks/index.cjs.map +1 -1
  161. package/dist/hooks/index.js +11 -6
  162. package/dist/index.cjs +20 -14
  163. package/dist/index.cjs.map +1 -1
  164. package/dist/index.js +364 -358
  165. package/dist/types/doc-source.d.ts +1 -31
  166. package/dist/types/doc-source.d.ts.map +1 -1
  167. package/dist/utils/index.cjs +0 -4
  168. package/dist/utils/index.cjs.map +1 -1
  169. package/dist/utils/index.d.ts +0 -1
  170. package/dist/utils/index.d.ts.map +1 -1
  171. package/dist/utils/index.js +1 -4
  172. package/dist/utils/index.js.map +1 -1
  173. package/package.json +1 -7
  174. package/src/components/chat/embeddable-chat.tsx +1 -1
  175. package/src/components/docs/doc-viewer.tsx +19 -111
  176. package/src/components/docs/index.ts +0 -17
  177. package/src/components/docs/use-document-tree.ts +0 -21
  178. package/src/components/embeds/embed-iframe.tsx +9 -7
  179. package/src/components/embeds/index.ts +0 -30
  180. package/src/components/embeds/og-link-preview.tsx +13 -13
  181. package/src/components/layout/page-layout.tsx +1 -14
  182. package/src/components/layout/title-block.tsx +62 -40
  183. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +3 -3
  184. package/src/components/shared/dev-section/dev-section-page.tsx +1 -9
  185. package/src/components/shared/dev-section/dev-section-view.tsx +9 -14
  186. package/src/components/shared/dev-section/index.ts +1 -1
  187. package/src/components/shared/doc-search/use-doc-search.ts +3 -7
  188. package/src/components/shared/legal-document/legal-document-page.tsx +2 -2
  189. package/src/components/shared/product-release/release-detail-page.tsx +4 -6
  190. package/src/components/ui/index.ts +0 -2
  191. package/src/components/ui/release-changelog-section.tsx +2 -7
  192. package/src/components/ui/simple-markdown-renderer.tsx +11 -7
  193. package/src/contexts/chat-runtime-context.tsx +0 -14
  194. package/src/types/doc-source.ts +1 -33
  195. package/src/utils/index.ts +0 -1
  196. package/dist/chunk-2QG57XOJ.js.map +0 -1
  197. package/dist/chunk-4PSQS3SW.cjs.map +0 -1
  198. package/dist/chunk-4TLE6VLU.js.map +0 -1
  199. package/dist/chunk-53FUMSZ5.cjs.map +0 -1
  200. package/dist/chunk-6C526VNN.cjs.map +0 -1
  201. package/dist/chunk-7OVGB2DQ.cjs.map +0 -1
  202. package/dist/chunk-F5OB2YAL.cjs +0 -144
  203. package/dist/chunk-F5OB2YAL.cjs.map +0 -1
  204. package/dist/chunk-FBWXMMRB.cjs +0 -2
  205. package/dist/chunk-FBWXMMRB.cjs.map +0 -1
  206. package/dist/chunk-FCDQNTDG.cjs.map +0 -1
  207. package/dist/chunk-FQOTC3UU.cjs.map +0 -1
  208. package/dist/chunk-GUTS7HGA.cjs.map +0 -1
  209. package/dist/chunk-GZ4C3XW6.js +0 -2
  210. package/dist/chunk-GZ4C3XW6.js.map +0 -1
  211. package/dist/chunk-IZ7JSBFP.js.map +0 -1
  212. package/dist/chunk-JALO4TAZ.js.map +0 -1
  213. package/dist/chunk-L6PSSIUQ.cjs.map +0 -1
  214. package/dist/chunk-PC746XCO.js.map +0 -1
  215. package/dist/chunk-PWQUAVA3.js.map +0 -1
  216. package/dist/chunk-SA2WPJVO.js +0 -144
  217. package/dist/chunk-SA2WPJVO.js.map +0 -1
  218. package/dist/chunk-UNVE2SDJ.cjs.map +0 -1
  219. package/dist/chunk-WMSTJAZT.cjs.map +0 -1
  220. package/dist/chunk-YBYI62OE.cjs.map +0 -1
  221. package/dist/components/case-studies/index.cjs +0 -126
  222. package/dist/components/case-studies/index.cjs.map +0 -1
  223. package/dist/components/case-studies/index.d.ts +0 -2
  224. package/dist/components/case-studies/index.d.ts.map +0 -1
  225. package/dist/components/case-studies/index.js +0 -126
  226. package/dist/components/case-studies/index.js.map +0 -1
  227. package/dist/components/case-studies/share-experience-section.d.ts +0 -48
  228. package/dist/components/case-studies/share-experience-section.d.ts.map +0 -1
  229. package/dist/components/docs/docs-hub-page.d.ts +0 -46
  230. package/dist/components/docs/docs-hub-page.d.ts.map +0 -1
  231. package/dist/components/docs/skeletons.d.ts +0 -32
  232. package/dist/components/docs/skeletons.d.ts.map +0 -1
  233. package/dist/components/docs/use-docs-resolve-link.d.ts +0 -20
  234. package/dist/components/docs/use-docs-resolve-link.d.ts.map +0 -1
  235. package/dist/components/embeds/embed-container.d.ts +0 -37
  236. package/dist/components/embeds/embed-container.d.ts.map +0 -1
  237. package/dist/components/embeds/file-download-card.d.ts +0 -18
  238. package/dist/components/embeds/file-download-card.d.ts.map +0 -1
  239. package/dist/components/embeds/linkedin-embed-client.d.ts +0 -8
  240. package/dist/components/embeds/linkedin-embed-client.d.ts.map +0 -1
  241. package/dist/components/embeds/markdown-image.d.ts +0 -5
  242. package/dist/components/embeds/markdown-image.d.ts.map +0 -1
  243. package/dist/components/embeds/reddit-embed-client.d.ts +0 -7
  244. package/dist/components/embeds/reddit-embed-client.d.ts.map +0 -1
  245. package/dist/components/embeds/rich-markdown-runtime.d.ts +0 -46
  246. package/dist/components/embeds/rich-markdown-runtime.d.ts.map +0 -1
  247. package/dist/components/embeds/twitter-embed-client.d.ts +0 -8
  248. package/dist/components/embeds/twitter-embed-client.d.ts.map +0 -1
  249. package/dist/components/layout/page-header.d.ts +0 -78
  250. package/dist/components/layout/page-header.d.ts.map +0 -1
  251. package/dist/components/layout/page-with-header.d.ts +0 -67
  252. package/dist/components/layout/page-with-header.d.ts.map +0 -1
  253. package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
  254. package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
  255. package/dist/utils/page-header-constants.d.ts +0 -15
  256. package/dist/utils/page-header-constants.d.ts.map +0 -1
  257. package/dist/utils/social-embed-cache.d.ts +0 -29
  258. package/dist/utils/social-embed-cache.d.ts.map +0 -1
  259. package/src/components/case-studies/index.ts +0 -4
  260. package/src/components/case-studies/share-experience-section.tsx +0 -185
  261. package/src/components/docs/docs-hub-page.tsx +0 -149
  262. package/src/components/docs/skeletons.tsx +0 -138
  263. package/src/components/docs/use-docs-resolve-link.ts +0 -52
  264. package/src/components/embeds/embed-container.tsx +0 -80
  265. package/src/components/embeds/file-download-card.tsx +0 -54
  266. package/src/components/embeds/linkedin-embed-client.tsx +0 -100
  267. package/src/components/embeds/markdown-image.tsx +0 -88
  268. package/src/components/embeds/reddit-embed-client.tsx +0 -550
  269. package/src/components/embeds/rich-markdown-runtime.tsx +0 -79
  270. package/src/components/embeds/twitter-embed-client.tsx +0 -308
  271. package/src/components/layout/page-header.tsx +0 -182
  272. package/src/components/layout/page-with-header.tsx +0 -110
  273. package/src/components/ui/rich-markdown-renderer.tsx +0 -1203
  274. package/src/utils/page-header-constants.ts +0 -15
  275. package/src/utils/social-embed-cache.ts +0 -391
  276. /package/dist/{chunk-PI4WSYQV.js.map → chunk-3ZXUQQL4.js.map} +0 -0
@@ -1,15 +0,0 @@
1
- /**
2
- * String constants shared by page-header consumers (`<PageHeader>`,
3
- * `<DevSectionPage>`, `<DocsHubPage>` callers). Lives in `utils/` —
4
- * NOT `'use client'` — so server modules (e.g. the hub's
5
- * `lib/docs/hub-docs-presets.tsx` that builds preset JSX with
6
- * `<Icon className={SECTION_HERO_ICON_CLASS} />`) can import the raw
7
- * string. Importing the constant from a `'use client'` module turns it
8
- * into a Next.js client-reference proxy in server contexts, which
9
- * lucide's internal `mergeClasses` then calls `.trim()` on and crashes.
10
- */
11
-
12
- /** Tailwind class applied uniformly to every section-hero / page-header
13
- * icon across the lib (Roadmap Map, Releases Rocket, Knowledge Hub
14
- * BookOpen, Data Room Building2, …). Yellow accent color, 40x40. */
15
- export const SECTION_HERO_ICON_CLASS = 'h-10 w-10 text-ods-accent'
@@ -1,391 +0,0 @@
1
- // Unified Social Media Embed Caching System
2
- // Shared between Reddit and Twitter embeds for consistent behavior.
3
- //
4
- // Lifted from hub `lib/utils/social-embed-cache.ts` so the lib's
5
- // RichMarkdownRenderer satellites (reddit/twitter embed clients) can
6
- // share it without an `@/lib/...` hub dependency. Pure-TS; no React.
7
- //
8
- // Endpoint paths are passed in via `apiEndpoint` on each call so embedders
9
- // can route through their own reverse proxy (e.g. `/content/api/blog/reddit-proxy`).
10
-
11
- // Global request deduplication to prevent duplicate caching requests
12
- const cachingRequests = new Map<string, Promise<void>>();
13
-
14
- // Global data cache to share fetched data between component instances
15
- const dataCache = new Map<string, any>();
16
-
17
- // Global refresh tracking to prevent duplicate background refreshes
18
- const refreshTimestamps = new Map<string, number>();
19
-
20
- interface CacheOptions {
21
- platform: 'reddit' | 'twitter';
22
- url: string;
23
- apiEndpoint: string;
24
- }
25
-
26
- export class SocialEmbedCache {
27
- private static instance: SocialEmbedCache;
28
-
29
- static getInstance(): SocialEmbedCache {
30
- if (!SocialEmbedCache.instance) {
31
- SocialEmbedCache.instance = new SocialEmbedCache();
32
- }
33
- return SocialEmbedCache.instance;
34
- }
35
-
36
- // Check if data exists in memory cache
37
- getFromMemory(url: string): any | null {
38
- return dataCache.get(url) || null;
39
- }
40
-
41
- // Store data in memory cache
42
- setInMemory(url: string, data: any): void {
43
- dataCache.set(url, data);
44
- }
45
-
46
- // Check server cache (cache-only mode)
47
- async getFromServer(options: CacheOptions): Promise<any | null> {
48
- try {
49
- console.log(`🔍 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Checking server cache for: ${options.url}`);
50
- const response = await fetch(`${options.apiEndpoint}?url=${encodeURIComponent(options.url)}&cache-only=true`);
51
-
52
- if (response.ok) {
53
- const data = await response.json();
54
- console.log(`✅ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache hit: ${options.url}`);
55
- return data;
56
- } else if (response.status === 404) {
57
- console.log(`💨 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] No server cache available for: ${options.url}`);
58
- return null;
59
- }
60
- } catch (error) {
61
- console.log(`❌ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache check failed for: ${options.url}`, error);
62
- }
63
- return null;
64
- }
65
-
66
- // Helper method to update server cache asynchronously
67
- private async updateServerCache(apiEndpoint: string, url: string, data: any): Promise<void> {
68
- try {
69
- console.log(`💾 [Cache] Updating server cache for: ${url}`);
70
- await fetch(`${apiEndpoint}?url=${encodeURIComponent(url)}`, {
71
- method: 'POST',
72
- headers: { 'Content-Type': 'application/json' },
73
- body: JSON.stringify(data)
74
- });
75
- console.log(`✅ [Cache] Server cache updated for: ${url}`);
76
- } catch (error) {
77
- console.log(`❌ [Cache] Server cache update failed for: ${url}`, error);
78
- }
79
- }
80
-
81
- // Store data in server cache (with deduplication)
82
- async setInServer(options: CacheOptions, data: any): Promise<void> {
83
- const cacheKey = `${options.platform}-${options.url}`;
84
-
85
- // Prevent duplicate caching requests
86
- if (cachingRequests.has(cacheKey)) {
87
- console.log(`⏭️ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache update skipped (already in progress): ${options.url}`);
88
- return;
89
- }
90
-
91
- console.log(`💾 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Updating server cache for: ${options.url}`);
92
- const cachePromise = fetch(`${options.apiEndpoint}?url=${encodeURIComponent(options.url)}`, {
93
- method: 'POST',
94
- headers: { 'Content-Type': 'application/json' },
95
- body: JSON.stringify(data)
96
- }).then(() => {
97
- console.log(`✅ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache updated successfully: ${options.url}`);
98
- cachingRequests.delete(cacheKey);
99
- }).catch((error) => {
100
- console.log(`❌ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Cache] Server cache update failed: ${options.url}`, error);
101
- cachingRequests.delete(cacheKey);
102
- });
103
-
104
- cachingRequests.set(cacheKey, cachePromise);
105
- await cachePromise;
106
- }
107
-
108
- // Direct fetch with platform-specific logic
109
- async fetchDirect(options: CacheOptions & {
110
- directFetcher: () => Promise<any>
111
- }): Promise<any | null> {
112
- try {
113
- console.log(`🔄 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Direct] Fetching content: ${options.url}`);
114
- const result = await options.directFetcher();
115
- console.log(`✅ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Direct] Fetch successful: ${options.url}`);
116
- return result;
117
- } catch (error) {
118
- console.log(`❌ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Direct] Fetch failed: ${options.url}`, error);
119
- return null;
120
- }
121
- }
122
-
123
- // Unified cache hierarchy: Memory → Server → Direct
124
- async fetchWithHierarchy(options: CacheOptions & {
125
- dataValidator: (data: any) => boolean;
126
- onDataUpdate: (data: any) => void;
127
- onError: (error: string) => void;
128
- onLoading: (loading: boolean) => void;
129
- }): Promise<void> {
130
- const { url, platform, onDataUpdate, onError, onLoading, dataValidator } = options;
131
- const platformName = platform.charAt(0).toUpperCase() + platform.slice(1);
132
-
133
- // Schedule background refresh for ALL loads (not just cache hits)
134
- this.scheduleAsyncRefresh(platform, url, dataValidator, options.apiEndpoint);
135
-
136
- // Step 1: Check memory cache first
137
- console.log(`🔍 [${platformName} Cache] Checking memory cache for: ${url}`);
138
- const memoryData = this.getFromMemory(url);
139
- if (memoryData && dataValidator(memoryData)) {
140
- console.log(`💎 [${platformName} Cache] Memory cache hit for: ${url}`);
141
- onDataUpdate(memoryData);
142
- onLoading(false);
143
- return;
144
- }
145
-
146
- // Step 2: Check server cache (file system cache)
147
- console.log(`🔍 [${platformName} Cache] No memory cache, checking server cache for: ${url}`);
148
- const serverCacheData = await this.getFromServer(options);
149
- if (serverCacheData && dataValidator(serverCacheData)) {
150
- console.log(`🎯 [${platformName} Cache] Server cache hit (cached data) for: ${url}`);
151
- onDataUpdate(serverCacheData);
152
- this.setInMemory(url, serverCacheData); // Update memory cache
153
- onLoading(false);
154
- return;
155
- }
156
-
157
- // Step 3: Direct fetch from browser (bypasses server proxy)
158
- console.log(`🔍 [${platformName} Cache] No server cache, attempting direct fetch for: ${url}`);
159
-
160
- // For Reddit, try direct browser fetch first (CORS enabled). Use
161
- // `AbortSignal.timeout(...)` to bound the wait — without it a hung
162
- // upstream (Reddit dropping the connection without RST, or a captive-
163
- // portal proxy stalling) would block this code path indefinitely and
164
- // never fall through to the server proxy.
165
- if (platform === 'reddit') {
166
- try {
167
- const directResponse = await fetch(url, {
168
- method: 'GET',
169
- headers: {
170
- 'Accept': 'application/json',
171
- },
172
- signal: AbortSignal.timeout(10_000),
173
- });
174
-
175
- if (directResponse.ok) {
176
- const directData = await directResponse.json();
177
- if (directData && dataValidator(directData)) {
178
- console.log(`✅ [${platformName} Direct] Browser direct fetch successful for: ${url}`);
179
- onDataUpdate(directData);
180
- this.setInMemory(url, directData);
181
- onLoading(false);
182
-
183
- // Async server cache update
184
- setTimeout(() => {
185
- this.updateServerCache(options.apiEndpoint, url, directData);
186
- }, 0);
187
- return;
188
- }
189
- }
190
- console.log(`💨 [${platformName} Direct] Browser direct fetch failed (${directResponse.status}), trying server proxy...`);
191
- } catch (directError) {
192
- console.log(`💨 [${platformName} Direct] Browser direct fetch failed, trying server proxy...`, directError);
193
- }
194
- }
195
-
196
- // Step 4: Server proxy as fallback
197
- console.log(`🔍 [${platformName} Cache] Trying server proxy for: ${url}`);
198
- try {
199
- const response = await fetch(`${options.apiEndpoint}?url=${encodeURIComponent(url)}`);
200
-
201
- if (response.ok) {
202
- const serverData = await response.json();
203
- if (serverData && dataValidator(serverData)) {
204
- // Check X-Cache header to determine if this was a true cache hit or fresh fetch
205
- const cacheStatus = response.headers.get('x-cache') || 'UNKNOWN';
206
- if (cacheStatus === 'HIT') {
207
- console.log(`🎯 [${platformName} Proxy] Server cache hit (cached data) for: ${url}`);
208
- } else if (cacheStatus === 'MISS-FETCHED') {
209
- console.log(`🔄 [${platformName} Proxy] Server processed fresh fetch (now cached) for: ${url}`);
210
- } else {
211
- console.log(`✅ [${platformName} Proxy] Server returned data (${cacheStatus}) for: ${url}`);
212
- }
213
- onDataUpdate(serverData);
214
- this.setInMemory(url, serverData);
215
- onLoading(false);
216
- return;
217
- } else {
218
- // 200 with a body that fails our shape check — throw so the
219
- // outer catch surfaces `onError` instead of silently dropping.
220
- console.log(`⚠️ [${platformName} Proxy] Server returned data but validation failed for: ${url}`);
221
- throw new Error('Server proxy returned invalid data shape');
222
- }
223
- } else {
224
- // The Response body is a one-shot stream — read it ONCE and branch
225
- // on shape afterwards. The previous code parsed 404s, fell through
226
- // to the generic-error path, and then called `response.json()` a
227
- // second time, which throws `TypeError: body stream already read`
228
- // and masks the real status with a confusing error.
229
- let errorData: { error?: string; message?: string } | null = null;
230
- try {
231
- errorData = await response.json();
232
- } catch {
233
- // Body wasn't JSON; errorData stays null and we fall through.
234
- }
235
- if (response.status === 404 && errorData?.error && errorData?.message) {
236
- // Structured 404 (e.g. an unavailable Reddit post). Surface
237
- // the proxy's message directly to the consumer.
238
- console.log(`🚫 [${platformName} Proxy] Content unavailable: ${errorData.message}`);
239
- onError(errorData.message);
240
- onLoading(false);
241
- return;
242
- }
243
- if (errorData?.error) {
244
- throw new Error(`Server error: ${errorData.error}`);
245
- }
246
- throw new Error(`Server proxy failed: ${response.status}`);
247
- }
248
- } catch (error) {
249
- console.log(`❌ [${platformName} Cache] All fetch attempts failed for: ${url}`, error);
250
- onError(`Unable to load ${platform} content`);
251
- onLoading(false);
252
- }
253
- }
254
-
255
- // Schedule async background refresh (runs once, 1 second after load)
256
- //
257
- // The hub's old version hard-coded `/api/blog/{reddit,twitter}-proxy`. We
258
- // now require `apiEndpoint` so embedders can route through their own
259
- // reverse proxy (e.g. `/content/api/blog/...`).
260
- private scheduleAsyncRefresh(
261
- platform: string,
262
- url: string,
263
- dataValidator: (data: any) => boolean,
264
- apiEndpoint: string,
265
- ): void {
266
- setTimeout(() => {
267
- const platformName = platform.charAt(0).toUpperCase() + platform.slice(1);
268
- console.log(`🔄 [${platformName} Async] Background refresh starting for: ${url}`);
269
-
270
- fetch(`${apiEndpoint}?url=${encodeURIComponent(url)}`)
271
- .then(response => {
272
- if (response.ok) {
273
- return response.json();
274
- } else {
275
- console.log(`❌ [${platformName} Async] Background refresh failed (${response.status}) for: ${url}`);
276
- return null;
277
- }
278
- })
279
- .then(data => {
280
- if (data && dataValidator(data)) {
281
- this.setInMemory(url, data);
282
- console.log(`✅ [${platformName} Async] Background refresh completed for: ${url}`);
283
- } else if (data) {
284
- console.log(`❌ [${platformName} Async] Background refresh failed (invalid data) for: ${url}`);
285
- }
286
- })
287
- .catch((error) => console.log(`❌ [${platformName} Async] Background refresh failed for: ${url}`, error));
288
- }, 1000); // Runs exactly once, 1 second after load
289
- }
290
-
291
- // Background refresh to keep cache fresh (with request deduplication)
292
- private async backgroundRefresh(options: CacheOptions & {
293
- directFetcher: () => Promise<any>;
294
- dataValidator: (data: any) => boolean;
295
- onDataUpdate: (data: any) => void;
296
- }, currentData: any): Promise<void> {
297
- const refreshKey = `refresh-${options.platform}-${options.url}`;
298
- const now = Date.now();
299
-
300
- // Check if we've refreshed this URL recently (within 30 seconds)
301
- const lastRefresh = refreshTimestamps.get(refreshKey);
302
- if (lastRefresh && (now - lastRefresh) < 30000) {
303
- console.log(`⏭️ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Refresh skipped (recent refresh): ${options.url}`);
304
- return;
305
- }
306
-
307
- // Prevent multiple background refresh requests for the same URL
308
- if (cachingRequests.has(refreshKey)) {
309
- console.log(`⏭️ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Refresh skipped (already in progress): ${options.url}`);
310
- return;
311
- }
312
-
313
- const refreshPromise = (async () => {
314
- try {
315
- console.log(`🔄 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Starting background refresh for: ${options.url}`);
316
- refreshTimestamps.set(refreshKey, now);
317
-
318
- const freshData = await this.fetchDirect(options);
319
-
320
- if (freshData && options.dataValidator(freshData)) {
321
- // Update cache silently without triggering UI re-render
322
- this.setInMemory(options.url, freshData);
323
- await this.setInServer(options, freshData);
324
-
325
- console.log(`✅ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Background refresh completed for: ${options.url}`);
326
- } else {
327
- console.log(`❌ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Background refresh failed (invalid data) for: ${options.url}`);
328
- }
329
- } catch (error) {
330
- console.log(`❌ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Background] Background refresh failed for: ${options.url}`, error);
331
- } finally {
332
- cachingRequests.delete(refreshKey);
333
- }
334
- })();
335
-
336
- cachingRequests.set(refreshKey, refreshPromise);
337
- await refreshPromise;
338
- }
339
-
340
- // Silent background refresh for UI-sensitive components
341
- private silentBackgroundRefresh(options: CacheOptions & {
342
- directFetcher: () => Promise<any>;
343
- dataValidator: (data: any) => boolean;
344
- }): void {
345
- const refreshKey = `silent-refresh-${options.platform}-${options.url}`;
346
- const now = Date.now();
347
-
348
- // Check if we've refreshed this URL recently (within 5 minutes for silent refresh)
349
- const lastRefresh = refreshTimestamps.get(refreshKey);
350
- if (lastRefresh && (now - lastRefresh) < 300000) {
351
- console.log(`⏭️ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh skipped (recent refresh): ${options.url}`);
352
- return; // Skip if refreshed within 5 minutes
353
- }
354
-
355
- // Prevent multiple silent refresh requests for the same URL
356
- if (cachingRequests.has(refreshKey)) {
357
- console.log(`⏭️ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh skipped (already in progress): ${options.url}`);
358
- return;
359
- }
360
-
361
- // Run in next tick to avoid blocking UI thread
362
- setTimeout(() => {
363
- const refreshPromise = (async () => {
364
- try {
365
- console.log(`🔄 [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Starting silent background refresh for: ${options.url}`);
366
- refreshTimestamps.set(refreshKey, now);
367
-
368
- // Fetch fresh data silently
369
- const freshData = await this.fetchDirect(options);
370
-
371
- if (freshData && options.dataValidator(freshData)) {
372
- // Update cache silently - no UI notifications
373
- this.setInMemory(options.url, freshData);
374
- await this.setInServer(options, freshData);
375
- console.log(`✅ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh completed for: ${options.url}`);
376
- } else {
377
- console.log(`❌ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh failed (invalid data) for: ${options.url}`);
378
- }
379
- } catch (error) {
380
- console.log(`❌ [${options.platform.charAt(0).toUpperCase() + options.platform.slice(1)} Silent] Silent refresh failed for: ${options.url}`, error);
381
- } finally {
382
- cachingRequests.delete(refreshKey);
383
- }
384
- })();
385
-
386
- cachingRequests.set(refreshKey, refreshPromise);
387
- }, 0); // Defer to next event loop tick
388
- }
389
- }
390
-
391
- export const socialCache = SocialEmbedCache.getInstance();