3
3
import { ArrowLeft , BookOpen , ExternalLink , Loader2 , Menu } from 'lucide-react' ;
4
4
import { usePathname } from 'next/navigation' ;
5
5
import { useEffect , useState } from 'react' ;
6
+ import { getPublicEnvVar } from '../../lib/env' ;
6
7
7
8
type UnifiedDocsWidgetProps = {
8
9
isActive : boolean ,
@@ -24,13 +25,32 @@ const PLATFORM_OPTIONS = [
24
25
{ value : 'python' , label : 'Python' , color : 'rgb(168, 85, 247)' } ,
25
26
] ;
26
27
28
+ // Get the docs base URL from environment variable with fallback
29
+ const getDocsBaseUrl = ( ) : string => {
30
+ // Use centralized environment variable system
31
+ const docsBaseUrl = getPublicEnvVar ( 'NEXT_PUBLIC_STACK_DOCS_BASE_URL' ) ;
32
+ if ( docsBaseUrl ) {
33
+ return docsBaseUrl ;
34
+ }
35
+
36
+ // Fallback logic for when env var is not set
37
+ if ( process . env . NODE_ENV === 'development' || window . location . hostname === 'localhost' ) {
38
+ return 'http://localhost:8104' ;
39
+ }
40
+
41
+ // Production fallback
42
+ return 'https://docs.stack-auth.com' ;
43
+ } ;
44
+
27
45
// Function to toggle sidebar in embedded docs via postMessage
28
46
const toggleEmbeddedSidebar = ( iframe : HTMLIFrameElement , visible : boolean ) => {
29
47
try {
30
- iframe . contentWindow ?. postMessage ( {
31
- type : 'TOGGLE_SIDEBAR' ,
32
- visible : visible
33
- } , '*' ) ;
48
+ const src = iframe . getAttribute ( "src" ) ?? "" ;
49
+ const targetOrigin = new URL ( src , window . location . origin ) . origin ;
50
+ iframe . contentWindow ?. postMessage (
51
+ { type : "TOGGLE_SIDEBAR" , visible } ,
52
+ targetOrigin
53
+ )
34
54
} catch ( error ) {
35
55
console . warn ( 'Failed to communicate with embedded docs:' , error ) ;
36
56
}
@@ -96,19 +116,19 @@ const getDocContentForPath = (path: string, docType: DocType, platform: string =
96
116
} ;
97
117
98
118
const docMapping = dashboardToDocsMap [ page ] ;
99
- const url = `http://localhost:8104 /docs-embed/${ docMapping . path } ` ;
119
+ const url = `${ getDocsBaseUrl ( ) } /docs-embed/${ docMapping . path } ` ;
100
120
const title = docMapping . title ;
101
121
return { title, url, type : 'dashboard' } ;
102
122
}
103
123
case 'docs' : {
104
124
// Default to getting started for main docs
105
- const url = `http://localhost:8104 /docs-embed/${ platform } /getting-started/setup` ;
125
+ const url = `${ getDocsBaseUrl ( ) } /docs-embed/${ platform } /getting-started/setup` ;
106
126
const title = 'Stack Auth Documentation' ;
107
127
return { title, url, type : 'docs' } ;
108
128
}
109
129
case 'api' : {
110
130
// Default to overview for API docs
111
- const url = `http://localhost:8104 /api-embed/overview` ;
131
+ const url = `${ getDocsBaseUrl ( ) } /api-embed/overview` ;
112
132
const title = 'API Reference' ;
113
133
return { title, url, type : 'api' } ;
114
134
}
@@ -131,15 +151,16 @@ export function UnifiedDocsWidget({ isActive }: UnifiedDocsWidgetProps) {
131
151
const [ canGoBack <
4B92
span class=pl-kos>, setCanGoBack ] = useState ( false ) ;
132
152
const [ iframeRef , setIframeRef ] = useState < HTMLIFrameElement | null > ( null ) ;
133
153
const [ isSidebarVisible , setIsSidebarVisible ] = useState ( false ) ;
154
+ const [ platformChangeSource , setPlatformChangeSource ] = useState < 'manual' | 'iframe' > ( 'manual' ) ;
134
155
135
156
// Load documentation when the component becomes active, doc type changes, platform changes, or pathname changes
136
157
useEffect ( ( ) => {
137
158
if ( isActive ) {
138
159
const newPageDoc = getDashboardPage ( pathname ) ;
139
160
140
- // If this is the first time opening, doc type changed, or platform changed
161
+ // If this is the first time opening, doc type changed, or platform changed manually (not from iframe)
141
162
if ( ! docContent || docContent . type !== selectedDocType ||
142
- ( selectedDocType !== 'api' && ! docContent . url . includes ( `/${ selectedPlatform } /` ) ) ) {
163
+ ( selectedDocType !== 'api' && ! docContent . url . includes ( `/${ selectedPlatform } /` ) && platformChangeSource === 'manual' ) ) {
143
164
setLoading ( true ) ;
144
165
setError ( null ) ;
145
166
setIframeLoaded ( false ) ;
@@ -167,9 +188,9 @@ export function UnifiedDocsWidget({ isActive }: UnifiedDocsWidgetProps) {
167
188
setShowSwitchPrompt ( true ) ;
168
189
}
169
190
}
170
- } , [ isActive , pathname , selectedDocType , selectedPlatform , docContent , currentPageDoc ] ) ;
191
+ } , [ isActive , pathname , selectedDocType , selectedPlatform , docContent , currentPageDoc , platformChangeSource ] ) ;
171
192
172
- // Monitor iframe for back button capability
193
+ // Monitor iframe for back button capability and platform detection
173
194
useEffect ( ( ) => {
174
195
// Simple heuristic: assume we can go back after the iframe has been loaded for a while
175
196
// and user has had time to navigate
@@ -182,6 +203,48 @@ export function UnifiedDocsWidget({ isActive }: UnifiedDocsWidgetProps) {
182
203
}
183
204
} , [ iframeLoaded ] ) ;
184
205
206
+ // Listen for platform changes from embedded docs via postMessage
207
+ useEffect ( ( ) => {
208
+ const handleMessage = ( event : MessageEvent ) => {
209
+ // Verify origin for security - allow localhost in dev and configured docs URL
210
+ const isLocalhost = event . origin . includes ( 'localhost' ) || event . origin . includes ( '127.0.0.1' ) ;
211
+ const expectedDocsOrigin = new URL ( getDocsBaseUrl ( ) ) . origin ;
212
+ const isValidDocsOrigin = event . origin === expectedDocsOrigin ;
213
+
214
+ if ( ! isLocalhost && ! isValidDocsOrigin ) return ;
215
+
216
+ if ( event . data ?. type === 'PLATFORM_CHANGE' ) {
217
+ const detectedPlatform = event . data . platform ;
218
+ const validPlatforms = PLATFORM_OPTIONS . map ( p => p . value ) ;
219
+
220
+ if ( validPlatforms . includes ( detectedPlatform ) && detectedPlatform !== selectedPlatform ) {
221
+ console . log ( 'Received platform change from iframe:' , detectedPlatform ) ;
222
+
223
+ // Mark this as an iframe-driven platform change
224
+ setPlatformChangeSource ( 'iframe' ) ;
225
+
226
+ // Update the platform selector but also update the docContent URL to reflect the current iframe URL
227
+ setSelectedPlatform ( detectedPlatform ) ;
228
+
229
+ // Update docContent to reflect the new URL without reloading the iframe
230
+ if ( docContent && event . data . pathname ) {
231
+ const newUrl = `${ getDocsBaseUrl ( ) } ${ event . data . pathname } ` ;
232
+ setDocContent ( {
233
+ ...docContent ,
234
+ url : newUrl
235
+ } ) ;
236
+ }
237
+
238
+ // Reset the platform change source after a short delay
239
+ setTimeout ( ( ) => setPlatformChangeSource ( 'manual' ) , 100 ) ;
240
+ }
241
+ }
242
+ } ;
243
+
244
+ window . addEventListener ( 'message' , handleMessage ) ;
245
+ return ( ) => window . removeEventListener ( 'message' , handleMessage ) ;
246
+ } , [ selectedPlatform , docContent ] ) ;
247
+
185
248
// Handle iframe load events
186
249
const handleIframeLoad = ( event : React . SyntheticEvent < HTMLIFrameElement > ) => {
187
250
setIframeLoaded ( true ) ;
@@ -232,6 +295,7 @@ export function UnifiedDocsWidget({ isActive }: UnifiedDocsWidgetProps) {
232
295
// Handle platform selection
233
296
const handlePlatformChange = ( platform : string ) => {
234
297
if ( platform !== selectedPlatform ) {
298
+ setPlatformChangeSource ( 'manual' ) ;
235
299
setSelectedPlatform ( platform ) ;
236
300
// Sidebar will automatically update to show new platform content
237
301
}
0 commit comments