@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.
- package/README.md +0 -9
- package/dist/chunk-26PKDALD.js +2379 -0
- package/dist/chunk-26PKDALD.js.map +1 -0
- package/dist/chunk-3MCHAFHB.js +89 -0
- package/dist/chunk-3MCHAFHB.js.map +1 -0
- package/dist/{chunk-PI4WSYQV.js → chunk-3ZXUQQL4.js} +2 -2
- package/dist/{chunk-WMSTJAZT.cjs → chunk-5E2HOSSH.cjs} +51 -913
- package/dist/chunk-5E2HOSSH.cjs.map +1 -0
- package/dist/{chunk-IL47XWV5.js → chunk-5P3B2LZW.js} +14 -8
- package/dist/{chunk-IL47XWV5.js.map → chunk-5P3B2LZW.js.map} +1 -1
- package/dist/chunk-66AANIOC.cjs +619 -0
- package/dist/chunk-66AANIOC.cjs.map +1 -0
- package/dist/{chunk-AD6C23QY.js → chunk-6GCI7JOE.js} +7 -8
- package/dist/{chunk-AD6C23QY.js.map → chunk-6GCI7JOE.js.map} +1 -1
- package/dist/chunk-6JINAOI7.cjs +311 -0
- package/dist/chunk-6JINAOI7.cjs.map +1 -0
- package/dist/{chunk-2QG57XOJ.js → chunk-7RIYT7ZH.js} +205 -1067
- package/dist/chunk-7RIYT7ZH.js.map +1 -0
- package/dist/{chunk-L6PSSIUQ.cjs → chunk-AQOWFSMB.cjs} +1 -1
- package/dist/chunk-AQOWFSMB.cjs.map +1 -0
- package/dist/chunk-BOCFIKYS.cjs +3009 -0
- package/dist/chunk-BOCFIKYS.cjs.map +1 -0
- package/dist/{chunk-54KNMC2R.cjs → chunk-D3LEFMOA.cjs} +3 -3
- package/dist/{chunk-54KNMC2R.cjs.map → chunk-D3LEFMOA.cjs.map} +1 -1
- package/dist/chunk-D652TJBQ.js +3009 -0
- package/dist/chunk-D652TJBQ.js.map +1 -0
- package/dist/{chunk-PWQUAVA3.js → chunk-E4XABBSU.js} +98 -338
- package/dist/chunk-E4XABBSU.js.map +1 -0
- package/dist/{chunk-JALO4TAZ.js → chunk-EL6QLAWX.js} +55 -357
- package/dist/chunk-EL6QLAWX.js.map +1 -0
- package/dist/{chunk-6C526VNN.cjs → chunk-EYEW6PTA.cjs} +118 -358
- package/dist/chunk-EYEW6PTA.cjs.map +1 -0
- package/dist/chunk-FQJK446R.js +1606 -0
- package/dist/chunk-FQJK446R.js.map +1 -0
- package/dist/{chunk-4PSQS3SW.cjs → chunk-GLLDTKZK.cjs} +9 -7
- package/dist/chunk-GLLDTKZK.cjs.map +1 -0
- package/dist/{chunk-FQOTC3UU.cjs → chunk-IE6OU3WQ.cjs} +16 -318
- package/dist/chunk-IE6OU3WQ.cjs.map +1 -0
- package/dist/chunk-J54Z3OCR.cjs +1606 -0
- package/dist/chunk-J54Z3OCR.cjs.map +1 -0
- package/dist/{chunk-PC746XCO.js → chunk-K2PFPBMF.js} +5563 -15048
- package/dist/chunk-K2PFPBMF.js.map +1 -0
- package/dist/chunk-KXCRGTRN.cjs +2379 -0
- package/dist/chunk-KXCRGTRN.cjs.map +1 -0
- package/dist/{chunk-IZ7JSBFP.js → chunk-LCNMR277.js} +1 -1
- package/dist/chunk-LCNMR277.js.map +1 -0
- package/dist/chunk-LFGGF7OT.cjs +449 -0
- package/dist/chunk-LFGGF7OT.cjs.map +1 -0
- package/dist/chunk-M2OCXTNT.js +311 -0
- package/dist/chunk-M2OCXTNT.js.map +1 -0
- package/dist/{chunk-L7ULJKG7.js → chunk-MBFWU2EM.js} +10 -6
- package/dist/{chunk-L7ULJKG7.js.map → chunk-MBFWU2EM.js.map} +1 -1
- package/dist/chunk-ME4EVDFP.js +619 -0
- package/dist/chunk-ME4EVDFP.js.map +1 -0
- package/dist/chunk-OQ6X7ZOC.js +449 -0
- package/dist/chunk-OQ6X7ZOC.js.map +1 -0
- package/dist/{chunk-4TLE6VLU.js → chunk-OY7OF7E7.js} +24 -30
- package/dist/chunk-OY7OF7E7.js.map +1 -0
- package/dist/chunk-POKKCWKF.js +354 -0
- package/dist/chunk-POKKCWKF.js.map +1 -0
- package/dist/{chunk-GUTS7HGA.cjs → chunk-QHIXS3W2.cjs} +2514 -11999
- package/dist/chunk-QHIXS3W2.cjs.map +1 -0
- package/dist/chunk-TFSYSWPS.cjs +89 -0
- package/dist/chunk-TFSYSWPS.cjs.map +1 -0
- package/dist/{chunk-53FUMSZ5.cjs → chunk-W6M2FLLT.cjs} +46 -40
- package/dist/chunk-W6M2FLLT.cjs.map +1 -0
- package/dist/{chunk-3JIQVE7T.js → chunk-WHMATDVP.js} +15 -9
- package/dist/{chunk-3JIQVE7T.js.map → chunk-WHMATDVP.js.map} +1 -1
- package/dist/{chunk-YBYI62OE.cjs → chunk-X647HY3F.cjs} +37 -33
- package/dist/chunk-X647HY3F.cjs.map +1 -0
- package/dist/{chunk-UNVE2SDJ.cjs → chunk-X6BV7MB7.cjs} +31 -37
- package/dist/chunk-X6BV7MB7.cjs.map +1 -0
- package/dist/{chunk-7OVGB2DQ.cjs → chunk-XREEV72C.cjs} +25 -19
- package/dist/chunk-XREEV72C.cjs.map +1 -0
- package/dist/chunk-YETA25JW.cjs +354 -0
- package/dist/chunk-YETA25JW.cjs.map +1 -0
- package/dist/{chunk-FCDQNTDG.cjs → chunk-YIGPRLQY.cjs} +20 -21
- package/dist/chunk-YIGPRLQY.cjs.map +1 -0
- package/dist/{chunk-X4DOXQRT.js → chunk-ZP4AVIZP.js} +6 -4
- package/dist/{chunk-X4DOXQRT.js.map → chunk-ZP4AVIZP.js.map} +1 -1
- package/dist/components/chat/index.cjs +18 -8
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +85 -75
- package/dist/components/contact/index.cjs +15 -8
- package/dist/components/contact/index.cjs.map +1 -1
- package/dist/components/contact/index.js +14 -7
- package/dist/components/docs/doc-viewer.d.ts +2 -39
- package/dist/components/docs/doc-viewer.d.ts.map +1 -1
- package/dist/components/docs/index.cjs +9 -17
- package/dist/components/docs/index.cjs.map +1 -1
- package/dist/components/docs/index.d.ts +0 -4
- package/dist/components/docs/index.d.ts.map +1 -1
- package/dist/components/docs/index.js +8 -16
- package/dist/components/docs/use-document-tree.d.ts.map +1 -1
- package/dist/components/embeds/embed-iframe.d.ts.map +1 -1
- package/dist/components/embeds/index.cjs +15 -38
- package/dist/components/embeds/index.cjs.map +1 -1
- package/dist/components/embeds/index.d.ts +0 -8
- package/dist/components/embeds/index.d.ts.map +1 -1
- package/dist/components/embeds/index.js +17 -40
- package/dist/components/faq/index.cjs +16 -9
- package/dist/components/faq/index.cjs.map +1 -1
- package/dist/components/faq/index.js +15 -8
- package/dist/components/features/index.cjs +16 -8
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +32 -24
- package/dist/components/index.cjs +452 -257
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +976 -781
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/page-layout.d.ts +1 -10
- package/dist/components/layout/page-layout.d.ts.map +1 -1
- package/dist/components/layout/title-block.d.ts +1 -17
- package/dist/components/layout/title-block.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +15 -7
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +17 -9
- package/dist/components/onboarding-guides/index.cjs +36 -35
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +14 -13
- package/dist/components/onboarding-guides/index.js.map +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +16 -9
- package/dist/components/related-content/index.cjs.map +1 -1
- package/dist/components/related-content/index.js +15 -8
- package/dist/components/shared/dev-section/dev-section-page.d.ts +0 -9
- package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -1
- package/dist/components/shared/dev-section/dev-section-view.d.ts.map +1 -1
- package/dist/components/shared/dev-section/index.d.ts +1 -1
- package/dist/components/shared/dev-section/index.d.ts.map +1 -1
- package/dist/components/shared/doc-search/use-doc-search.d.ts.map +1 -1
- package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +112 -100
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +32 -20
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +52 -50
- package/dist/components/ui/file-manager/index.cjs.map +1 -1
- package/dist/components/ui/file-manager/index.js +6 -4
- package/dist/components/ui/file-manager/index.js.map +1 -1
- package/dist/components/ui/index.cjs +19 -13
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +0 -2
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +139 -133
- package/dist/components/ui/release-changelog-section.d.ts +2 -6
- package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
- package/dist/components/ui/simple-markdown-renderer.d.ts +8 -2
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
- package/dist/contexts/chat-runtime-context.d.ts +0 -14
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +3 -3
- package/dist/contexts/index.js +5 -5
- package/dist/embed-shims/index.cjs +3 -3
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +4 -4
- package/dist/hooks/index.cjs +9 -4
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +11 -6
- package/dist/index.cjs +20 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +364 -358
- package/dist/types/doc-source.d.ts +1 -31
- package/dist/types/doc-source.d.ts.map +1 -1
- package/dist/utils/index.cjs +0 -4
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -4
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -7
- package/src/components/chat/embeddable-chat.tsx +1 -1
- package/src/components/docs/doc-viewer.tsx +19 -111
- package/src/components/docs/index.ts +0 -17
- package/src/components/docs/use-document-tree.ts +0 -21
- package/src/components/embeds/embed-iframe.tsx +9 -7
- package/src/components/embeds/index.ts +0 -30
- package/src/components/embeds/og-link-preview.tsx +13 -13
- package/src/components/layout/page-layout.tsx +1 -14
- package/src/components/layout/title-block.tsx +62 -40
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +3 -3
- package/src/components/shared/dev-section/dev-section-page.tsx +1 -9
- package/src/components/shared/dev-section/dev-section-view.tsx +9 -14
- package/src/components/shared/dev-section/index.ts +1 -1
- package/src/components/shared/doc-search/use-doc-search.ts +3 -7
- package/src/components/shared/legal-document/legal-document-page.tsx +2 -2
- package/src/components/shared/product-release/release-detail-page.tsx +4 -6
- package/src/components/ui/index.ts +0 -2
- package/src/components/ui/release-changelog-section.tsx +2 -7
- package/src/components/ui/simple-markdown-renderer.tsx +11 -7
- package/src/contexts/chat-runtime-context.tsx +0 -14
- package/src/types/doc-source.ts +1 -33
- package/src/utils/index.ts +0 -1
- package/dist/chunk-2QG57XOJ.js.map +0 -1
- package/dist/chunk-4PSQS3SW.cjs.map +0 -1
- package/dist/chunk-4TLE6VLU.js.map +0 -1
- package/dist/chunk-53FUMSZ5.cjs.map +0 -1
- package/dist/chunk-6C526VNN.cjs.map +0 -1
- package/dist/chunk-7OVGB2DQ.cjs.map +0 -1
- package/dist/chunk-F5OB2YAL.cjs +0 -144
- package/dist/chunk-F5OB2YAL.cjs.map +0 -1
- package/dist/chunk-FBWXMMRB.cjs +0 -2
- package/dist/chunk-FBWXMMRB.cjs.map +0 -1
- package/dist/chunk-FCDQNTDG.cjs.map +0 -1
- package/dist/chunk-FQOTC3UU.cjs.map +0 -1
- package/dist/chunk-GUTS7HGA.cjs.map +0 -1
- package/dist/chunk-GZ4C3XW6.js +0 -2
- package/dist/chunk-GZ4C3XW6.js.map +0 -1
- package/dist/chunk-IZ7JSBFP.js.map +0 -1
- package/dist/chunk-JALO4TAZ.js.map +0 -1
- package/dist/chunk-L6PSSIUQ.cjs.map +0 -1
- package/dist/chunk-PC746XCO.js.map +0 -1
- package/dist/chunk-PWQUAVA3.js.map +0 -1
- package/dist/chunk-SA2WPJVO.js +0 -144
- package/dist/chunk-SA2WPJVO.js.map +0 -1
- package/dist/chunk-UNVE2SDJ.cjs.map +0 -1
- package/dist/chunk-WMSTJAZT.cjs.map +0 -1
- package/dist/chunk-YBYI62OE.cjs.map +0 -1
- package/dist/components/case-studies/index.cjs +0 -126
- package/dist/components/case-studies/index.cjs.map +0 -1
- package/dist/components/case-studies/index.d.ts +0 -2
- package/dist/components/case-studies/index.d.ts.map +0 -1
- package/dist/components/case-studies/index.js +0 -126
- package/dist/components/case-studies/index.js.map +0 -1
- package/dist/components/case-studies/share-experience-section.d.ts +0 -48
- package/dist/components/case-studies/share-experience-section.d.ts.map +0 -1
- package/dist/components/docs/docs-hub-page.d.ts +0 -46
- package/dist/components/docs/docs-hub-page.d.ts.map +0 -1
- package/dist/components/docs/skeletons.d.ts +0 -32
- package/dist/components/docs/skeletons.d.ts.map +0 -1
- package/dist/components/docs/use-docs-resolve-link.d.ts +0 -20
- package/dist/components/docs/use-docs-resolve-link.d.ts.map +0 -1
- package/dist/components/embeds/embed-container.d.ts +0 -37
- package/dist/components/embeds/embed-container.d.ts.map +0 -1
- package/dist/components/embeds/file-download-card.d.ts +0 -18
- package/dist/components/embeds/file-download-card.d.ts.map +0 -1
- package/dist/components/embeds/linkedin-embed-client.d.ts +0 -8
- package/dist/components/embeds/linkedin-embed-client.d.ts.map +0 -1
- package/dist/components/embeds/markdown-image.d.ts +0 -5
- package/dist/components/embeds/markdown-image.d.ts.map +0 -1
- package/dist/components/embeds/reddit-embed-client.d.ts +0 -7
- package/dist/components/embeds/reddit-embed-client.d.ts.map +0 -1
- package/dist/components/embeds/rich-markdown-runtime.d.ts +0 -46
- package/dist/components/embeds/rich-markdown-runtime.d.ts.map +0 -1
- package/dist/components/embeds/twitter-embed-client.d.ts +0 -8
- package/dist/components/embeds/twitter-embed-client.d.ts.map +0 -1
- package/dist/components/layout/page-header.d.ts +0 -78
- package/dist/components/layout/page-header.d.ts.map +0 -1
- package/dist/components/layout/page-with-header.d.ts +0 -67
- package/dist/components/layout/page-with-header.d.ts.map +0 -1
- package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
- package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
- package/dist/utils/page-header-constants.d.ts +0 -15
- package/dist/utils/page-header-constants.d.ts.map +0 -1
- package/dist/utils/social-embed-cache.d.ts +0 -29
- package/dist/utils/social-embed-cache.d.ts.map +0 -1
- package/src/components/case-studies/index.ts +0 -4
- package/src/components/case-studies/share-experience-section.tsx +0 -185
- package/src/components/docs/docs-hub-page.tsx +0 -149
- package/src/components/docs/skeletons.tsx +0 -138
- package/src/components/docs/use-docs-resolve-link.ts +0 -52
- package/src/components/embeds/embed-container.tsx +0 -80
- package/src/components/embeds/file-download-card.tsx +0 -54
- package/src/components/embeds/linkedin-embed-client.tsx +0 -100
- package/src/components/embeds/markdown-image.tsx +0 -88
- package/src/components/embeds/reddit-embed-client.tsx +0 -550
- package/src/components/embeds/rich-markdown-runtime.tsx +0 -79
- package/src/components/embeds/twitter-embed-client.tsx +0 -308
- package/src/components/layout/page-header.tsx +0 -182
- package/src/components/layout/page-with-header.tsx +0 -110
- package/src/components/ui/rich-markdown-renderer.tsx +0 -1203
- package/src/utils/page-header-constants.ts +0 -15
- package/src/utils/social-embed-cache.ts +0 -391
- /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();
|
|
File without changes
|